例として.Netを使用してDSLをアサートする

何らかの形で複雑なシステムでのテストの有用性を否定する人はいません。 テストを行わなければ、すぐに混乱に陥り、ほとんどの時間をデバッガーで費やし、アプリケーションの一部または別の部分の変更からの間接的な効果を検索してトラップできます。 テキストでは、テストが重要であり、必要です。



科学では、テストはシステムのドキュメントです。 よく書かれたテストにより、システムの動作方法、動作方法が明確になります。これらはすべて、システムの動作に関する既製の仕様として読む必要があります。 つまり 理想的には、一貫性のあるわかりやすいテキストを入手する必要があります。 これは、テスト自体が既にビジネス言語で記述されている場合、単体テストから始まり、行動テスト/受け入れテストで最もよく現れるテスト方法が徐々に近づいているという理想です(この時点でFitnesseを思い出します)。



テストを書くとき、コードやクラスの行をスキップしないでください。それらを正しく構成することが重要です。 テストクラスが1つのテストメソッドのみで構成されているのはごく普通のことです。恥ずかしがらずに、これは20画面のクラスよりもはるかに優れています。 HDスクリーン。



一般的に、すべての関係が明確に見えるように、すべてがテストの最大の明快さと明快さを目的とする必要があります。 そのため、1つのテストのみを使用してプログラムロジックを復元できます。 読みやすさは、DSL(ドメイン固有言語)のアサートだけでなく、ファイルの命名、 アレンジアクトアサートアプローチにも適用されます。 結局のところ、これらはすべて新しいアプローチではありませんが、周りのプロジェクトで私が見ているものから判断すると、まだ広く知られていません。 はい、私自身が偶然新しいトピックに出会い StructureMapのソースコードを調べました。



苦労しないように、テストを改善するために提供される主な手順をすぐに説明します。



リストされている多くのポイントのほとんどはニュースではなく、ほとんどすべてが実際の開発で使用されていると思います。







広い意味で、これはアレンジ アクト アサートパラダイムに適合し、テスト、アクション、および検証の準備を明確に区別する必要があることを示唆しています。 この場合、各テストクラスがテストの特定の準備を記述することがわかります。 理想的には、SetUpまたはFixtureSetUpでActが設定され、テストで結果が既にチェックされている-アサートします。



例でこれを示すのが最善です。



ゲーム内の海賊のアクションと機能の実装であるPirateクラスを作成しましょう。 海賊はフィールドを動き回り、金を奪い去り、戦い、泳ぎ、殺し、死ぬことができます。 彼ができることはたくさんあり、すべてのテストを1つのファイルにまとめて、テストを地域ごとに区切ることは間違っています。 たとえば、いくつかのテストクラスを実行することをお勧めします。



うーん、海賊には別のクラスでそれらに捧げるような枝分かれした方法やより多くの方法がありません。 しかし一方で、より責任のある方法がある競技場があります。 Fieldクラスが運動場の作成と動きの一般的な制御を担当するとします。 彼のテストは次のようになります。



その後、クラス内のテストには次のように名前が付けられます。



次に、テストと結果を表示するときに、[ゲーム] フィールドを 作成する [ it] 海上で フィールドを 作成する [ フィールドの] 境界として読むことができます-それはほとんど純粋な英語です。 海賊版では、メソッド名にもう少し書く必要があります。



Whenという単語でテストクラスに名前を付けることができる場合 、これはArrange Act Assertの純粋な例です。 例:



例:

[TestFixture] public class WhenCreateField { private Field field; private TestEmptyRules rule; [TestFixtureSetUp] public void ClassInit() { // Arrange, Act rule = new TestEmptyRules(); field = rule.Field; } [Test] public void MaxSizeShouldBeDefined() { //Assert Position.MaxColumn.ShouldBeEqual(rule.Size); Position.MaxRow.ShouldBeEqual(rule.Size); } [Test] public void FieldShouldBeCreated() { //Assert field.ShouldBeNotNull(); } [Test] public void ItShouldBeGrassByDefault() { //Assert field.GetPlayableArea() .ShouldContain() .OnlyCellsOf(CellType.Grass); } … }
      
      





すべての準備はテストクラスの初期化で行われ、チェックのみが行われます。



1つのテスト-1つのテスト。 これは上記の例で見ることができます。 何がどのようにテストされ、最終的に何が結果になるのかがすぐにわかります。 多くの場合、既存のテストにAssertを追加するのは魅力的です-これは「バニーを追加」と呼ばれます。そのため、リファクタリング後にテストを上げると「うさぎ」が非常に思いつき、余分な時間を奪います。



次に、テストの構造が非常に重要です。 比較する:

 [Test] public void HeMayKillFoes() { var airplaneCell = new AirplaneCell(4, 5); var player = Black.Pirate; var foe = Red.Pirate; airplaneCell.PirateComing(foe); airplaneCell.PirateComing(player); airplaneCell.Pirates.ShouldContain().Exact(player); } [Test] public void HeMayKillFoes() { //Arrange var airplaneCell = new AirplaneCell(4, 5); var player = Black.Pirate; var foe = Red.Pirate; airplaneCell.PirateComing(foe); //Act airplaneCell.PirateComing(player); //Assert airplaneCell.Pirates.ShouldContain().Exact(player); }
      
      





テストは単純であり、シンプルである必要がありますが、私の意見では、最初のオプションは、準備がどこに進んでいるのか、主要な作業方法をどこでテストするのか、検証自体をどこで行うのかを理解するためにより多くの時間が必要です。 2番目のバージョンでは、視線はコンポーネントの境界を簡単に定義し、心は重要なポイントをすぐに認識します。 そうすることで、あなた自身がテストをレビューしやすくなり、正確にテストされているものを覚えやすくなります。



別の例:

  [Test] public void AtSecondTimeHeCanNotTransferToShip() { //Arrange var airplaneCell = new AirplaneCell(4, 5); var pirate = Black.Pirate; airplaneCell.PirateComing(pirate); //Act airplaneCell.Transfer(); airplaneCell.PirateComing(pirate); airplaneCell.Transfer(); //Assert pirate.State.ShouldBeEqual(PlayerState.Free); }
      
      





キーポイントが強調表示され、テストが作成されたケースがすぐにわかります。 このアプローチの回廊テスト中に、Actがテストを書く上で最も議論の余地があることが判明しました。 多くの場合、テストセットアップに含める必要があるものとテスト対象のアクションに含まれるものをさまざまな方法で見る人がいます。 同じ点がAAAに捧げられたすべての記事で言及されており、その答えもそこに与えられています。私の同僚と私はそれを必要としました。 はい、ありがとうキャップ! 厳密なルールはありません。



今、最も注意深い筆記試験の仲間がおそらくすでに気づいている重要なポイント。 テストでは、明示的なAssertコンストラクトはありません。



正直なところ、 最初の衝動はテストする必要のあるプロパティを記述することなので、このアプローチがはるかに好きです。 その後、InteliSenseのショートカットを切断するために使用したいAssertを入力する必要があることを既に理解しています。 たとえば、Assert.AreEqual / Assert.That($ actual $、Is.EqualTo($ expected $))はaaeでした 。 この組み合わせを入力し、Tabを押すと、コードに既にテンプレートがあります。 しかし、これは不便です。ReSharperを設定する必要がありました。assertが最初に来ることを覚えておいてください。



拡張メソッドを記述するという点で言語の可能性を使用して使用する方がはるかに便利です。 したがって、ReSharper、CodeAssist、または他のシステムを使用しているかどうかに関係なく、すべての開発者にとって常にヒントがあります。







上の図では、コードが直感的なセマンティックレベルで記述されていることがわかります。 最初の衝動は、テスト用の値を記述することです。次に、それをどのように比較するかを考え、最後のステップは目的の値を記録することです。 IntelliSenseは、どのタイプの期待値を確認するかを通知することに注意してください。







この図は、nUnitの検証を記述するための標準的なアプローチを示しています。 Assert.Thatユーティリティコードが最初に来ることに注意してください。 検証用の値を書き込むとき、IntelliSenseは常に役立つとは限りません。 この図は、ドロップダウンリストの項目にスクロールしない実際の作業を示しています。 検証用の値を書き込んだ後、再度、サービスワードを「記憶」し、検証の種類を書き、期待値を入力するとIntelliSenseは無力になります。



次に、2つのアプローチを視覚的に比較します。

 [Test] public void FieldShouldBeCreated() { //Assert field.ShouldBeNotNull(); } [Test] public void FieldShouldBeCreated() { //Assert Assert.IsNotNull(field); }
      
      





そして別の例:

 [Test] public void MaxSizeShouldBeDefined() { // DSL Position.MaxColumn.ShouldBeEqual(rule.Size); Position.MaxRow.ShouldBeEqual(rule.Size); // nUnit Assert.That(Position.MaxColumn, Is.EqualTo(rule.Size)); Assert.That(Position.MaxRow, Is.EqualTo(rule.Size)); // MSTest Assert.AreEqual(Position.MaxColumn, rule.Size); Assert.That(Position. MaxRow, rule.Size); }
      
      





拡張メソッドが使用される最初のオプションは、より理解しやすく読みやすいように思えます。 主なプラスは、それが何であり、何が期待されているかが明確に見えることです 。 nUnitはこの点で優れており、アプローチの違いをある程度排除しますが、MSTestと比較すると天地です。 MSTestでは、通常、初心者には期待される結果の場所と結果の場所が明確ではありません。



テストをテストするための拡張メソッドを書くことのもう1つの利点は、ドメインモデルと一致する論理構造です。 これは、例を挙げて再度説明するのが最適です。

 [Test] public void ItShouldBeGrassByDefault() { //Assert field.GetPlayableArea() .ShouldContain() .OnlyCellsOf(CellType.Grass); } [Test] public void ItShouldBeGrassByDefault() { //Assert var playableArea = field.GetPlayableArea(); Assert.IsTrue(playableArea.All(cells => cells.CellType == CellType.Grass)); //or another case Assert.IsTrue(field.GetPlayableArea() .All(cells => cells.CellType == CellType.Grass)); }
      
      





どちらの方法がより明確ですか?



特定の用途向けに独自のテストDSLを開発することは難しくないと思います。 しかし、例として(まだ洗練されていない):

 public static CollectionAssertCases ShouldContain(this IEnumerable enumerable) { return new CollectionAssertCases(enumerable); } public class CollectionAssertCases { private readonly IEnumerable enumerable; public List AsList { get { return new List(enumerable); } } public CollectionAssertCases(IEnumerable enumerable) { this.enumerable = enumerable; } public void Elements(params T[] elements) { Assert.That(enumerable, Is.EquivalentTo(elements)); } } public static void OnlyCellsOf(this CollectionAssertCases collection, CellType cellType) { Assert.IsTrue(collection.AsList.All(c => c.CellType == cellType)); }
      
      





テスト用のDSLがよりわかりやすく簡潔になっているという事実に加えて、技術的に重要でない詳細を取り除くことができます。 コードの重複を減らし、テスト作成の速度を上げます。



別の有用なアプリケーションは、コードの技術的なテストで見つけることができます。 たとえば、クラスのプロパティが突然書き込み可能になりたくないので、簡単にテストを作成できます。

 [Test] public void PiratesStateCanNotBeSetDirectly() { //Arrange var pirate = Black.Pirate; //Assert pirate.Property("State").ShouldBeReadonly(); }
      
      





リフレクションを使用する場合、テストは不要なメソッドやアクションで乱雑になりませんが、特定のプロパティの使用に同意できない大規模または経験の浅いチームで作業している場合は、APIの変更を制御できます。 これは、「確実な」と同じように機能します。 テストは、反射の拒絶に麻痺している同僚にとっても特に鮮やかな感情を引き起こさないことに同意します。



繰り返しになりますが、同僚の廊下テストの結果によれば、習慣の力は素晴らしいことであることがわかりました。テストをすぐに読んで書いてみるのは少し珍しいことです。 目は静的クラスAssertを探しており、わずかな戸惑いの原因を見つけませんが、何らかの理由でコメント内の単語Assertが急いでいないことがわかりました。 それにも関わらず、開発者には心の柔軟性がなければならないため、これはすぐに進むと思います。



説明したアプローチの利点は次のとおりです。



短所では、私は含まれます:






All Articles