初心者向けのTDD。 一般的な質問への回答

TDDを使用して記述されたプロジェクトのソース。 Visual Studio 2008 / C#

xUnitライブラリーを使用してテストを作成し、 Moqを使用してモックオブジェクトを作成しました。








TDDについて尋ねる次のインタビューで、テストによる開発の基本的なアイデアでさえほとんどの開発者に理解されていないという結論に達しました。 このトピックの無知は、プログラマーにとって大きな省略だと思います。



TDDについて多くの質問があります。 これらの質問から、重要なものを選択し、それらに対する答えを書きました。 質問自体はテキストで確認できますが、斜体で記載されています。



出発点として、ビジネス上の問題を解決します。



タスクは複数のサブタスクで構成されています。1)レポートを送信するコンソールアプリケーションを作成します。 2)生成された2番目のレポートも監査員に送信する必要があります。 3)レポートが生成されていない場合、レポートがないというメッセージを経営陣に送信します。 4)すべてのレポートを送信した後、コンソールに送信された数を表示する必要があります。



質問:TDDを使用してコードの記述を開始する場所



1つの小さな問題を解決することから始めます。 テストを使用して、ビジネス要件をコードで記述します。





そこで、新しいコンソールアプリケーションプロジェクトを作成しました(最終コードはSVNからダウンロードできます)。 これで、コードは1行もありません。 私のアプリケーションが一般的にどのように機能するかを理解する必要があります。 注意、はじめに! 4番目の要件を説明するテストを作成します。



public class ReporterTests

{

[Fact]

public void ReturnNumberOfSentReports()

{

var reporter = new Reporter();



var reportCount = reporter.SendReports();



Assert.Equal(2, reportCount);

}

}




* This source code was highlighted with Source Code Highlighter .








Assertクラスは、送信されたレポートの数が2に等しいかどうかを確認します。テストは、 xUnitコンソールユーティリティまたはVisual Studioプラグインによって起動されます。



アプリケーションのAPIを設計しました。 ReporterオブジェクトとSendReports関数を使用します。 SendReports関数は、送信されたレポートの数を返します。これは、Assert.Equalステートメントを使用したテストで表示されます。 変数reportCountが2に等しくない場合、テストは失敗します。



これで設計の最初の段階は終わり、コーディングに移ります。 このテストを機能させるために最小限のコードを書きましょう。



質問まず、多くのテストを作成し、それらを1つずつ修正する必要があります

いいえ、順番に行きましょう。 私たちは努力を吹き飛ばしません。 各時点で、アイドルテストは1つだけです。 このテストで修正するコードを作成し、次のテストを作成できます。





現在、プロジェクトにはReporterTestsクラスが1つしかありません。 テスト対象のReporterクラスを作成します。 Reporterオブジェクトをプロジェクトに追加し、空のSendReports関数を作成します。 テストに合格するには、 SendReports関数が数値2を返す必要があります。SendReports関数数値2を返すようにReporterオブジェクトに初期条件を設定する方法はまだ明確ではありません。



設計に戻ります。 レポートを作成するための別のクラスと、レポートを送信するためのクラスがあると思います。 Reporterオブジェクト自体が、これらのクラスの相互作用ロジックを制御します。 最初のオブジェクトにIReportBuilder 、2番目のオブジェクトにIReportSenderという名前を付けましょう。 設計されたので、コードを作成します。



[Fact]

public void ReturnNumberOfSentReports()

{

IReportBuilder reportBuilder;

IReportSender reportSender;



var reporter = new Reporter(reportBuilder, reportSender);



var reportCount = reporter.SendReports();



Assert.Equal(2, reportCount);

}




* This source code was highlighted with Source Code Highlighter .








質問:テストメソッドの命名規則はありますか?

はい、あります。 テストメソッドの名前は、テストと期待する結果をチェックすることを示すことが望ましいです。 この場合、名前は「送信されたレポートの数が返される」ことを示しています。





これらのインターフェースを実装するクラスがどのように機能するかは今では重要ではありません。 主なことは、すべてのレポートをIReportBuilderで生成し、それらをIReportSenderを使用して送信できることです



質問:特定のクラスを作成するのではなく、IReportBuilderおよびIReportSenderインターフェイスを使用する価値があるのはなぜですか?

レポートを作成するためのオブジェクトとレポートを送信するためのオブジェクトを実装するには、さまざまな方法があります。 これらのクラスの将来の実装をインターフェースの背後に隠すほうが便利です。



質問:テストクラスが対話するオブジェクトの動作を設定する方法は?

テストクラスが対話する実際のオブジェクトの代わりに、スタブまたはモックオブジェクトを使用するのが最も便利です。 現在のアプリケーションでは、Moqライブラリを使用してモックオブジェクトを作成します。





[Fact]

public void ReturnNumberOfSentReports()

{

var reportBuilder = new Mock<IReportBuilder>();

var reportSender = new Mock<IReportSender>();



// IReportBuilder

// : " CreateReports List<Report> 2 "

reportBuilder.Setup(m => m.CreateRegularReports())

.Returns( new List <Report> { new Report(), new Report()});



var reporter = new Reporter(reportBuilder.Object, reportSender.Object);



var reportCount = reporter.SendReports();



Assert.Equal(2, reportCount);

}




* This source code was highlighted with Source Code Highlighter .








テストを実行します-SendReports関数を実装しなかったため、テストはパスしません。 最も簡単な実装をプログラムします。



public class Reporter

{

private readonly IReportBuilder reportBuilder;

private readonly IReportSender reportSender;



public Reporter(IReportBuilder reportBuilder, IReportSender reportSender)

{

this .reportBuilder = reportBuilder;

this .reportSender = reportSender;

}



public int SendReports()

{

return reportBuilder.CreateRegularReports().Count;

}

}




* This source code was highlighted with Source Code Highlighter .








テストを実行すると、合格します。 4番目の要件を実装しました。 同時に、彼らはそれをテストの形で記録しました。 したがって、システムのドキュメントを作成します。 実践が示しているように、この文書はいつでも最も関連性が高く、期限切れになることはありません。 どうぞ



質問:テストを作成するための標準テンプレートはありますか?

はい Arrange-Act-Assert(AAA)と呼ばれます。 つまり テストは3つの部分で構成されます。 配置-テスト用の入力をインストールします。 行為-結果をテストするアクションを実行します。 アサート-実行結果を確認します。 次のテストで対応する手順に署名します。





次に、最初の要件であるレポートの送信について説明します。 テストでは、生成されたすべてのレポートが送信されることを確認します。



[Fact]

public void SendAllReports()

{

// arrange

var reportBuilder = new Mock<IReportBuilder>();

var reportSender = new Mock<IReportSender>();



reportBuilder.Setup(m => m.CreateRegularReports())

.Returns( new List <Report> { new Report(), new Report()});



var reporter = new Reporter(reportBuilder.Object, reportSender.Object);



// act

reporter.SendReports();



// assert

reportSender.Verify(m => m.Send(It.IsAny<Report>()), Times.Exactly(2));

}




* This source code was highlighted with Source Code Highlighter .








質問:1つのテストクラスですべてのアプリケーションオブジェクトのテストを記述する必要がありますか?

非常に望ましくない。 この場合、テストクラスは巨大なサイズに成長します。 テストするクラスごとに個別のテストファイルを作成することをお勧めします。





SendReports関数にレポートの送信を実装していないため、テストを実行しますが、合格しません。 これで、いつものように、設計を終了し、コーディングに進みます。



public int SendReports()

{

IList<Report> reports = reportBuilder.CreateRegularReports();



foreach (Report report in reports)

{

reportSender.Send(report);

}



return reports.Count;

}




* This source code was highlighted with Source Code Highlighter .








テストを実行します-両方とも合格します。 別のビジネス要件を実装しました。 さらに、両方のテストを実行することにより、 5分前に行った機能を壊さないようにしました



質問:すべてのテストを実行する頻度はどれくらいですか?

より頻繁に、より良い。 コードの変更は、システムの他の部分に予期せぬ影響を与える可能性があります。 特にこのコードがあなたによって書かれていない場合。 理想的には、すべてのテストは、プロジェクトの各ビルドで継続的インテグレーションシステムによって自動的に実行される必要があります。



質問:プライベートメソッドをテストする方法は?

ここまで読んだ場合、テストが最初に記述されてからコードが記述されるため、クラス内のすべてのコードがデフォルトでテストされることを既に理解しています。





3番目の要件を実装する方法について考えます。 どこから始めますか? UML図を描くか、椅子に座って瞑想するだけですか? テストから始めましょう! コードに3番目のビジネス要件を記述します。



[Fact]

public void SendSpecialReportToAdministratorIfNoReportsCreated()

{

var reportBuilder = new Mock<IReportBuilder>();

var reportSender = new Mock<IReportSender>();



reportBuilder.Setup(m => m.CreateRegularReports()).Returns( new List <Report>());

reportBuilder.Setup(m => m.CreateSpecialReport()).Returns( new SpecialReport());



var reporter = new Reporter(reportBuilder.Object, reportSender.Object);



reporter.SendReports();



reportSender.Verify(m => m.Send(It.IsAny<Report>()), Times.Never());

reportSender.Verify(m => m.Send(It.IsAny<SpecialReport>()), Times.Once());

}




* This source code was highlighted with Source Code Highlighter .








開始し、テストが失敗することを確認します。 現在、私たちの努力はこのテストを修正することを目的としています。 ここで、設計は通常どおり終了し、プログラミングに戻ります。



public int SendReports()

{

IList<Report> reports = reportBuilder.CreateRegularReports();



if (reports.Count == 0)

{

reportSender.Send(reportBuilder.CreateSpecialReport());

}



foreach (Report report in reports)

{

reportSender.Send(report);

}



return reports.Count;

}




* This source code was highlighted with Source Code Highlighter .








テストを実行します-3つのテストすべてに合格します。 新しい関数を実装しましたが、古い関数を壊しませんでした。 これは喜ばしいことです!



質問:どのコードが既にテストされているかを知るにはどうすればよいですか?

テストでのコードカバレッジは、さまざまなユーティリティを使用して確認できます。 手始めに、 PartCoverにアドバイスできます。



質問:コードをテスト100%でカバーするよう努力すべきですか?

いや これは、そのようなテストを作成するのに多大な労力を必要とし、それらをサポートするためにさらに多くの労力を要します。 通常のカバレッジの範囲は50〜90%です。 つまり データベース、外部サービス、およびファイルシステムにアクセスせずに、すべてのビジネスロジックをカバーする必要があります。





2つ目の要件を自分で実装し、 SendReports関数の最後の部分とテストをコメントで共有することをお勧めします。 最初にテストを書きますよね?



質問:データベースとの相互作用をテストし、SMTPサーバーまたはファイルシステムを操作するにはどうすればよいですか?

実際、テストする必要があります。 しかし、これは単体テストでは行われません。 ユニットテストはすぐに合格し、外部ソースに依存しないためです。 それ以外の場合は、週に1回実行します。 これについては、 「効果的な単体テスト」の記事で詳しく説明しています。



質問:TDDはいつ適用できますか?

TDDを使用して、任意のアプリケーションを作成できます。 新しいライブラリまたはプログラミング言語の機能を検討している場合は、非常に便利です。 アプリケーションには特別な境界はありません。 マルチスレッドおよびその他の特定のアプリケーションのテストには不便な場合があります。





おわりに





すべての開発者がこの方法を試してみてください。 その後、TDDが個人およびプロジェクト全体にどのように適しているかを決定できます。



All Articles