ユニットテストについて利害関係者やクライアントと話し合うとき、ユニットテストに関して多くの混乱と疑問が生じることがよくあります。ユニットテストは、デンタルフロスが子供に行うように聞こえることがあります。 「私はすでに歯を磨いています、なぜ私はこれをする必要があるのですか?」
単体テストを提案することは、テスト方法とユーザー受け入れテストが十分に強力であると考える人々にとって、不必要な費用のように聞こえることがよくあります。
しかし、ユニットテストは非常に強力なツールであり、想像以上に簡単です。この記事では、単体テストと、DotNetで利用できるツール(次のようなツール)について説明します。 Microsoft.VisualStudio.TestTools そして Moq 。
フィボナッチ数列のn番目の項を計算する単純なクラスライブラリの構築を試みます。そのためには、数値を加算するカスタム数学クラスに依存するフィボナッチ数列を計算するためのクラスを作成する必要があります。次に、.NET Testing Frameworkを使用して、プログラムが期待どおりに実行されることを確認できます。
単体テストは、プログラムをコードの最小ビット(通常は関数レベル)に分解し、関数が期待する値を返すことを確認します。単体テストフレームワークを使用することにより、単体テストは別個のエンティティになり、プログラムの構築時にプログラムで自動テストを実行できます。
[TestClass] public class FibonacciTests { [TestMethod] //Check the first value we calculate public void Fibonacci_GetNthTerm_Input2_AssertResult1() { //Arrange int n = 2; //setup Mock mockMath = new Mock(); mockMath .Setup(r => r.Add(It.IsAny(), It.IsAny())) .Returns((int x, int y) => x + y); UnitTests.Fibonacci fibonacci = new UnitTests.Fibonacci(mockMath.Object); //Act int result = fibonacci.GetNthTerm(n); //Assert Assert.AreEqual(result, 1); } }
Arrange、Act、Assertの方法論テストを使用した単純な単体テストで、数学ライブラリが2 +2を正しく追加できることをテストします。
単体テストが設定された後、コードに変更が加えられた場合、たとえば、プログラムが最初に開発されたときにはわからなかった追加の条件を考慮して、単体テストはすべてのケースが期待値と一致するかどうかを示します。関数によって出力されます。
ユニットテストは ない 統合テスト。です ない エンドツーエンドのテスト。これらは両方とも強力な方法論ですが、代替としてではなく、単体テストと組み合わせて機能する必要があります。
ユニットテストの最も難しい利点は理解できますが、最も重要なのは、変更されたコードをその場で再テストできることです。理解するのがとても難しい理由は、非常に多くの開発者が自分で考えているからです。 「二度とその機能に触れることはありません」 または 「終わったら再テストします。」 そして、利害関係者は、 「その作品がすでに書かれているのに、なぜそれを再テストする必要があるのですか?」
開発スペクトルの両側にいる人として、私はこれらの両方のことを言いました。私の中の開発者は、なぜそれを再テストしなければならないのかを知っています。
私たちが日常的に行う変更は、大きな影響を与える可能性があります。例えば:
ユニットテストはこれらの質問を受け取り、コードと 処理する これらの質問に常に答えられるようにするため。ビルドの前に単体テストを実行して、新しいバグが発生していないことを確認できます。単体テストはアトミックに設計されているため、非常に高速に実行され、通常はテストごとに10ミリ秒未満です。非常に大規模なアプリケーションでも、完全なテストスイートを1時間以内に実行できます。 UATプロセスはそれに一致しますか?
Fibonacci_GetNthTerm_Input2_AssertResult1
以外これは最初の実行であり、セットアップ時間を含みます。すべての単体テストは5ミリ秒未満で実行されます。ここでの私の命名規則は、テストしたいクラスまたはクラス内のメソッドを簡単に検索するように設定されています
しかし、開発者としては、これはあなたにとってより多くの作業のように聞こえるかもしれません。はい、リリースするコードが優れているという安心感が得られます。しかし、単体テストは、設計がどこに弱いのかを確認する機会も提供します。 2つのコードに対して同じ単体テストを作成していますか?代わりに、それらを1つのコードに含める必要がありますか?
あなたの ユニットテスト可能にするコード それ自体があなたのデザインを改善する方法です。また、単体テストを行ったことがない、またはコーディングする前に設計を検討する時間があまりないほとんどの開発者にとって、単体テストの準備をすることで、設計がどれだけ改善されるかを理解できます。
DRYの他に、他の考慮事項もあります。
予想よりも長く実行される非常に複雑な単体テストを作成する必要がある場合は、メソッドが複雑すぎて、複数のメソッドとして適している可能性があります。
テスト対象のメソッドが別のクラスまたは関数を必要とする場合、これを依存関係と呼びます。単体テストでは、依存関係が内部で何をしているのかは気にしません。テスト対象のメソッドの目的上、これはブラックボックスです。依存関係には、その動作が適切に機能しているかどうかを判断する独自の単体テストのセットがあります。
テスターとして、その依存関係をシミュレートし、特定のインスタンスで返す値を指示する必要があります。これにより、テストケースをより細かく制御できます。これを行うには、その依存関係のダミー(または後で説明するようにモック)バージョンを挿入する必要があります。
依存性と依存性の注入を理解すると、コードに循環依存性が導入されていることに気付く場合があります。クラスAがクラスBに依存し、クラスBがクラスAに依存している場合は、設計を再検討する必要があります。
私たちのことを考えてみましょう フィボナッチの例 。上司は、C#で使用可能な現在の追加演算子よりも効率的で正確な新しいクラスがあると言っています。
この特定の例は現実の世界ではあまりありそうにありませんが、認証、オブジェクトマッピング、ほとんどすべてのアルゴリズムプロセスなど、他のコンポーネントにも同様の例があります。この記事の目的のために、クライアントの新しい追加機能が、コンピューターが発明されて以来、最新かつ最高であると仮定しましょう。
そのため、上司から、単一のクラスMath
を持つブラックボックスライブラリが渡され、そのクラスでは、単一の関数Add
が渡されます。フィボナッチ計算機を実装するあなたの仕事は、次のようになる可能性があります。
public int GetNthTerm(int n) { Math math = new Math(); int nMinusTwoTerm = 1; int nMinusOneTerm = 1; int newTerm = 0; for (int i = 2; i これは恐ろしいことではありません。新しいMath
をインスタンス化しますクラスを作成し、それを使用して前の2つの用語を追加し、次の用語を取得します。この方法は、通常の一連のテストを実行し、100項まで計算し、1000項、10,000項などを計算して、方法論が正常に機能することに満足するまで続けます。その後、将来のある時点で、ユーザーは第501軍団が期待どおりに機能していないと不満を漏らします。あなたは夜を過ごしてコードを調べ、このコーナーケースが機能しない理由を理解しようとします。あなたは最新かつ最高のMath
に疑いを持ち始めますクラスは上司が思っているほど素晴らしいものではありません。しかし、それはブラックボックスであり、それを実際に証明することはできません。内部的に行き詰まりになっています。
ここでの問題は、依存関係Math
です。フィボナッチ計算機には注入されません。したがって、テストでは、Math
からの既存の、テストされていない、不明な結果に常に依存します。フィボナッチをテストします。 Math
に問題がある場合、フィボナッチは常に間違っています(501番目の用語の特別なケースをコーディングしないで)。
この問題を修正するためのアイデアは、Math
を注入することです。フィボナッチ計算機にクラス分けします。しかし、さらに良いのは、Math
のインターフェースを作成することです。パブリックメソッド(この場合はAdd
)を定義し、Math
にインターフェイスを実装するクラスクラス。
public interface IMath { int Add(int x, int y); } public class Math : IMath { public int Add(int x, int y) { //super secret implementation here } } }
Math
を注入するのではなくクラスをフィボナッチに挿入すると、IMath
を注入できます。フィボナッチへのインターフェース。ここでの利点は、独自のOurMath
を定義できることです。正確であることがわかっているクラスで、それに対して電卓をテストします。さらに良いことに、Moqを使用すると、Math.Add
を簡単に定義できます。戻り値。合計の数を定義することも、Math.Add
と言うこともできます。 x + yを返します。
private IMath _math; public Fibonacci(IMath math) { _math = math; }
IMathインターフェースをフィボナッチクラスに注入します
//setup Mock mockMath = new Mock(); mockMath .Setup(r => r.Add(It.IsAny(), It.IsAny())) .Returns((int x, int y) => x + y);
Moqを使用して何を定義するかMath.Add
戻り値。
これで、2つの数値を加算するための実証済みの真の方法ができました(C#でその+演算子が間違っていると、より大きな問題が発生します)。新しいモックIMath
を使用して、501番目の用語の単体テストをコーディングし、実装を間違えたかどうか、またはカスタムMath
を確認できます。クラスにはもう少し作業が必要です。
メソッドにやりすぎをさせないでください
この例は、メソッドの実行が多すぎるという考えも示しています。確かに、加算はかなり単純な操作であり、GetNthTerm
からその機能を抽象化する必要はほとんどありません。方法。しかし、操作がもう少し複雑な場合はどうなりますか?追加の代わりに、おそらくそれはモデルの検証、操作するオブジェクトを取得するためのファクトリの呼び出し、またはリポジトリから追加の必要なデータの収集でした。
ほとんどの開発者は、1つの方法には1つの目的があるという考えに固執しようとします。単体テストでは、単体テストはアトミックメソッドに適用する必要があるという原則に固執し、メソッドに多くの操作を導入することで、テスト不能にするようにしています。関数を適切にテストするために非常に多くのテストを作成しなければならないという問題が発生することがよくあります。
メソッドに追加する各パラメーターは、パラメーターの複雑さに応じて指数関数的に記述しなければならないテストの数を増やします。ロジックにブール値を追加する場合は、現在のテストとともに真と偽のケースをチェックする必要があるため、書き込むテストの数を2倍にする必要があります。モデル検証の場合、単体テストの複雑さは非常に急速に増大する可能性があります。

私たちは皆、メソッドに少し余分なものを追加することで罪を犯しています。しかし、これらのより大きく、より複雑な方法では、ユニットテストが多すぎる必要があります。そして、ユニットテストを書くと、メソッドがやりすぎであることがすぐに明らかになります。入力パラメータから考えられる結果が多すぎると感じた場合は、メソッドを一連の小さなメソッドに分割する必要があるという事実を考慮してください。
繰り返さないでください
プログラミングの私たちのお気に入りのテナントの1つ。これはかなり簡単なはずです。同じテストを複数回作成していることに気付いた場合は、コードを複数回導入しています。その作業を、使用しようとしている両方のインスタンスがアクセスできる共通のクラスにリファクタリングすると便利な場合があります。
どのユニットテストツールが利用できますか?
DotNetは、箱から出してすぐに使用できる非常に強力な単体テストプラットフォームを提供します。これを使用して、として知られているものを実装することができます 方法論を整理し、行動し、主張する 。最初の考慮事項を調整し、テスト対象のメソッドを使用してそれらの条件に基づいて行動し、何かが起こったことを表明します。何でもアサートできるため、このツールはさらに強力になります。メソッドが特定の回数呼び出されたこと、メソッドが特定の値を返したこと、特定のタイプの例外がスローされたこと、またはその他の考えられることを表明できます。より高度なフレームワークをお探しの方のために、NUnitとそれに対応するJava JUnit 実行可能なオプションです。
[TestMethod] //Test To Verify Add Never Called on the First Term public void Fibonacci_GetNthTerm_Input0_AssertAddNeverCalled() { //Arrange int n = 0; //setup Mock mockMath = new Mock(); mockMath .Setup(r => r.Add(It.IsAny(), It.IsAny())) .Returns((int x, int y) => x + y); UnitTests.Fibonacci fibonacci = new UnitTests.Fibonacci(mockMath.Object); //Act int result = fibonacci.GetNthTerm(n); //Assert mockMath.Verify(r => r.Add(It.IsAny(), It.IsAny()), Times.Never); }
フィボナッチメソッドが例外をスローして負の数を処理することをテストします。単体テストでは、例外がスローされたことを確認できます。
依存性注入を処理するには、両方 注入する そして Unity DotNetプラットフォームに存在します。この2つにはほとんど違いがなく、FluentSyntaxまたはXMLConfigurationのどちらで構成を管理するかが問題になります。
依存関係をシミュレートするには、Moqをお勧めします。 Moqは手に入れるのが難しい場合がありますが、要点は、依存関係のモックバージョンを作成することです。次に、特定の条件下で何を返すかを依存関係に指示します。たとえば、Square(int x)
という名前のメソッドがあるとします。これは整数の2乗であり、x = 2の場合は4を返します。また、任意の整数に対してx ^ 2を返すように指示することもできます。または、x = 2の場合に5を返すように指示することもできます。最後のケースを実行するのはなぜですか?テストの役割のメソッドが依存関係からの回答を検証することである場合、バグを適切にキャッチしていることを確認するために、無効な回答を強制的に返すことができます。
[TestMethod] //Test To Verify Add Called Three times on the fifth Term public void Fibonacci_GetNthTerm_Input4_AssertAddCalledThreeTimes() { //Arrange int n = 4; //setup Mock mockMath = new Mock(); mockMath .Setup(r => r.Add(It.IsAny(), It.IsAny())) .Returns((int x, int y) => x + y); UnitTests.Fibonacci fibonacci = new UnitTests.Fibonacci(mockMath.Object); //Act int result = fibonacci.GetNthTerm(n); //Assert mockMath.Verify(r => r.Add(It.IsAny(), It.IsAny()), Times.Exactly(3)); }
Moqを使用してモックを伝えるIMath
インターフェース処理方法Add
テスト中。 It.Is
で明示的なケースを設定できますまたはIt.IsInRange
の範囲。
DotNetのユニットテストフレームワーク
Microsoftユニットテストフレームワーク
ザ・ Microsoftユニットテストフレームワーク は、Microsoftが提供する、すぐに使用できる単体テストソリューションであり、VisualStudioに含まれています。 VSが付属しているため、うまく統合できます。プロジェクトを開始すると、Visual Studioは、アプリケーションと一緒に単体テストライブラリを作成するかどうかを尋ねてきます。
Microsoft Unit Testing Frameworkには、テスト手順をより適切に分析するのに役立つツールも多数付属しています。また、マイクロソフトが所有・作成しているため、今後もある程度の安定感があります。
しかし、Microsoftツールを使用すると、それらが提供するものを手に入れることができます。 Microsoft Unit Testing Frameworkは、統合が面倒な場合があります。
NUnit
使用する上での最大の利点 NUnit パラメータ化されたテストです。上記のフィボナッチの例では、いくつかのテストケースを入力して、それらの結果が正しいことを確認できます。また、501番目の問題の場合は、いつでも新しいパラメーターセットを追加して、新しいテストメソッドを必要とせずにテストが常に実行されるようにすることができます。
NUnitの主な欠点は、VisualStudioに統合することです。 Microsoftバージョンに付属しているベルやホイッスルがなく、独自のツールセットをダウンロードする必要があります。
xUnit.Net
xUnit C#は、既存の.NETエコシステムと非常にうまく統合されているため、非常に人気があります。 Nugetには、xUnitの多くの拡張機能があります。また、Team Foundation Serverとうまく統合されていますが、いくつあるかはわかりません。 .NET開発者 さまざまなGit実装でTFSを引き続き使用します。
欠点として、多くのユーザーがxUnitのドキュメントが少し不足していると不満を漏らしています。ユニットテストを初めて使用するユーザーにとって、これは大きな頭痛の種となる可能性があります。さらに、xUnitの拡張性と適応性により、NUnitやMicrosoftのユニットテストフレームワークよりも学習曲線が少し急になります。
テスト駆動開発/開発
テスト駆動設計/開発(TDD) 独自の投稿に値するもう少し高度なトピックです。しかし、私は紹介を提供したかった。
アイデアは、ユニットテストから始めて、ユニットテストに何が正しいかを伝えることです。次に、それらのテストを中心にコードを記述できます。理論的には概念は単純に聞こえますが、実際には、アプリケーションについて後ろ向きに考えるように脳を訓練することは非常に困難です。ただし、このアプローチには、事後に単体テストを作成する必要がないという組み込みの利点があります。これにより、リファクタリング、書き換え、およびクラスの混乱が少なくなります。
TDDは近年少し流行語になっていますが、採用は遅れています。その概念的な性質は、利害関係者を混乱させ、承認を得るのを困難にします。しかし、開発者として、プロセスに慣れるためにTDDアプローチを使用して小さなアプリケーションを作成することをお勧めします。
ユニットテストが多すぎない理由
単体テストは、開発者が自由に使える最も強力なテストツールの1つです。アプリケーションの完全なテストには決して十分ではありませんが、回帰テスト、コード設計、および目的の文書化におけるその利点は比類のないものです。
ユニットテストを書きすぎるようなことはありません。各エッジケースは、ソフトウェアの将来的に大きな問題を提案する可能性があります。見つかったバグを単体テストとして記憶することで、それらのバグが後のコード変更中にソフトウェアに忍び寄る方法を見つけられないようにすることができます。プロジェクトの初期予算に10〜20%を追加することもできますが、トレーニング、バグ修正、およびドキュメント化でそれよりもはるかに節約できます。
この記事で使用されているBitbucketリポジトリを見つけることができます ここに 。
基本を理解する
ユニットテストとはどういう意味ですか?
個々のメソッドをテストするプロセスをユニットテストします。ユニットテストは、各コンポーネントが意図したとおりに機能することを確認します。
ユニットテストの例は何ですか?
既知の値をその関数に渡し、期待される結果を表明することによってアトミック関数をテストするプロセスを単体テストすることは、関数によって生成されます。
ユニットテストはどのように実行されますか?
単体テストは、コードベースのフレームワークに合わせて調整されたフレームワークとツールセットを使用して実行されます。
良い単体テストとは何ですか?
優れた単体テストとは、メソッドへのすべての可能な入力を網羅するテストです。これらの方法の結果は設計者に知られている必要があり、単体テストは期待される結果を反映している必要があります。
ユニットテストの利点は何ですか?
単体テストは、テスト可能なコードの記述を要求することでコードの品質を保証し、コードの機能を保証し、回帰テストの優れた開始点を提供します。
ユニットテストはそれだけの価値がありますか?
はい。単体テストは開発コストを少し増やす可能性がありますが、長期的には、回帰テストとバグの記憶を通じて時間と労力を節約できます。
ユニットテストはいくつ書く必要がありますか?
理想的には、単体テストはあらゆる可能性でコードのあらゆる側面をカバーする必要があります。もちろん、実際にはそれは不可能であり、簡単な答えは、ユニットテストをあまり多く書くことは決してできないということです。