「アレンジ-アサート-アクト-アサート」にする必要がありますか?


94

Arrange-Act-Assertの古典的なテストパターンに関しては、Actの前にカウンターアサーションを追加することがよくあります。このようにして、通過するアサーションがアクションの結果として実際に通過していることがわかります。

これは、red-green-refactorの赤に似ていると思います。テスト中に赤いバーが表示された場合にのみ、緑色のバーは、違いをもたらすコードを記述したことを意味します。合格したテストを作成すると、どのコードでもそれを満たします。同様に、Arrange-Assert-Act-Assertに関しても、最初のアサーションが失敗した場合、どのActも最終的なAssertを通過したことになるので、実際にはActについて何も検証していません。

テストはこのパターンに従っていますか?なぜですか、なぜそうではありませんか?

明確化の更新:最初のアサーションは基本的に最終アサーションの反対です。それはアレンジが機能したという主張ではありません。Actがまだ機能していないという主張です。

回答:


121

これは最も一般的なことではありませんが、独自の名前を付けるのに十分一般的です。この手法は、ガードアサーションと呼ばれます。490ページの詳細な説明は、Gerard Meszaros 著の優れたxUnit Test Patterns(強く推奨)にあります。

通常、私はこのパターンを使用しません。私が確認する必要があると思う前提条件を検証する特定のテストを作成する方が正しいためです。このようなテストは、前提条件が満たされない場合は常に失敗するはずです。つまり、他のすべてのテストに組み込む必要はありません。1つのテストケースで検証できるのは1つだけなので、これにより、問題をより適切に分離できます。

特定のテストケースで満たす必要のある前提条件が多数ある場合があるため、複数のGuardアサーションが必要になる場合があります。すべてのテストでそれらを繰り返す代わりに、各前提条件に対して1つ(そして1つだけ)のテストを行うことで、テストコードをより管理しやすくなります。


+1、非常に良い答え。最後の部分は、物事を個別の単体テストとして保護できることを示しているため、特に重要です。
murrekatt 2011

3
私は通常、この方法でも実行しましたが、前提条件を確認するための個別のテスト(特に、要件が変化する大規模なコードベースの場合)に問題があります-前提条件テストは時間の経過とともに変更され、「メイン」と同期しなくなりますそれらの前提条件を前提とするテスト。したがって、前提条件はすべて正常で緑色である可能性がありますが、これらの前提条件はメインテストで満たされていません。メインテストでは常に緑色で正常に表示されます。しかし、前提条件がメインテストにあれば、それらは失敗したでしょう。この問題に遭遇し、それに対する素晴らしい解決策に遭遇しましたか?
nchaud 2014年

2
テストを頻繁に変更すると、テストの信頼性が低下する傾向があるため、他の問題が発生する可能性あります。要件の変化に直面しても、追加のみの方法でコードを設計することを検討してください。
Mark Seemann、2014年

@MarkSeemannそのとおりです。繰り返しを最小限に抑える必要がありますが、反対側には多くのことがあり、特定のテストのアレンジに影響を与える可能性がありますが、アレンジ自体のテストは通過します。たとえば、アレンジテストのクリーンアップ、または別のテストの後のクリーンアップは失敗し、アレンジはアレンジテストと同じではありません。
レクシーノ2017年


8

ここに例があります。

public void testEncompass() throws Exception {
    Range range = new Range(0, 5);
    assertFalse(range.includes(7));
    range.encompass(7);
    assertTrue(range.includes(7));
}

Range.includes()単にtrueを返すように書いたのかもしれません。私はしませんでしたが、持っているかもしれないと想像することができます。または、他の方法でそれを間違って書いた可能性があります。私はTDDで私が実際にそれを正しく理解したことを願って期待します-それincludes()はうまくいく-しかし多分私はしなかった したがって、最初のアサーションは健全性チェックであり、2番目のアサーションが本当に意味があることを確認します。

それ自体を読んで、assertTrue(range.includes(7));「変更された範囲に7が含まれていることを表明する」と言っています。最初のアサーションのコンテキストで読むと、次のように述べています。「include ()呼び出すと、7が含まれることをアサートします。また、includeはテストしているユニットなので、これは(小さい)値だと思います。

私は自分の答えを受け入れています。他の多くは、セットアップのテストについての私の質問を誤解しました。これは少し違うと思います。


例を挙げてくれてありがとう、カール。さて、TDDサイクルの赤い部分では、include()が実際に何かを行うまでです。最初のアサーションは無意味です。それは2番目のアサーションの複製にすぎません。緑で、それは有用になり始めます。リファクタリング中にそれは理にかなっています。これを自動的に行うUTフレームワークがあるとよいでしょう。
切手09

あなたがそのRangeクラスをTDDしているとしたら、それを壊すときにRangeのctorをテストする別の失敗したテストはないでしょうか?
切手09

1
@philippe:質問が理解できるかどうかわかりません。Rangeコンストラクターとcontains()には、独自の単体テストがあります。詳しく説明してもらえますか?
カールマナスター2009

最初のassertFalse(range.includes(7))アサーションが失敗するためには、範囲コンストラクターに欠陥がある必要があります。したがって、私はRangeコンストラクターのテストがそのアサーションと同時に壊れないかどうかを尋ねるつもりでした。そして、別の値でActの後にアサートするのはどうですか:例えばassertFalse(range.includes(6))?
慈善団体、2009

1
私の考えでは、範囲の構築は、includes()のような関数の前に来ます。したがって、私は同意しますが、不完全なコンストラクタ(または誤ったinclude())だけが最初のアサーションを失敗させますが、コンストラクタのテストには、includes()への呼び出しは含まれません。はい、最初のアサーションまでのすべての関数はすでにテストされています。しかし、この最初の否定的な主張は何かを、そして私の心には何か有用なものを伝えています。それが最初に書かれたときに、そのようなすべてのアサーションが通過したとしても。
カールマナスター2009

7

Arrange-Assert-Act-Assertテストでは、常に2つのテストにリファクタリングすることができます。

1. Arrange-Assert

そして

2. Arrange-Act-Assert

最初のテストはアレンジフェーズで設定されたものに対してのみアサートし、2番目のテストはアクトフェーズで発生したものに対してのみアサートします。

これには、失敗したのがアレンジフェーズかアクトフェーズかについてより正確なフィードバックを提供できるという利点がArrange-Assert-Act-Assertありますが、オリジナルではこれらが混同されているため、より深く掘り下げて、どのアサーションが失敗したのか、なぜ失敗したのかを正確に調べて、失敗したのはアレンジまたはアクトでした。

また、テストをより小さな独立したユニットに分離しているため、ユニットテストの意図をよりよく満たします。

最後に、異なるテストで同様のArrangeセクションが表示された場合は、これらを共有ヘルパーメソッドに引き出して、将来的にテストがより乾燥して保守しやすくなるようにしてください。


3

私は今これをやっています。異なる種類のAAAA

Arrange - setup
Act - what is being tested
Assemble - what is optionally needed to perform the assert
Assert - the actual assertions

更新テストの例:

Arrange: 
    New object as NewObject
    Set properties of NewObject
    Save the NewObject
    Read the object as ReadObject

Act: 
    Change the ReadObject
    Save the ReadObject

Assemble: 
    Read the object as ReadUpdated

Assert: 
    Compare ReadUpdated with ReadObject properties

その理由は、ACTがReadUpdatedの読み取りを含まないようにするためです。これは、ACTが行為の一部ではないためです。行為は変化し、保存するだけです。だから本当に、アサーションのためのARRANGE ReadUpdated、私はアサーションのためにASSEMBLEを呼び出しています。これは、ARRANGEセクションの混乱を防ぐためです。

ASSERTにはアサーションのみを含める必要があります。これにより、アサートを設定するACTとASSERTの間にASSEMBLEが残ります。

最後に、アレンジで失敗した場合、これらの些細なバグを防止/発見するために他のテストが必要であるため、テストは正しくありません。私が提示するシナリオでは、READとCREATEをテストする他のテストがすでにあるはずです。「Guardアサーション」を作成すると、DRYが壊れてメンテナンスが作成される可能性があります。


1

テストしているアクションを実行する前に状態を検証するための「健全性チェック」アサーションを投げるのは古い手法です。私は通常、それらをテストの足場として記述して、テストが期待どおりに機能することを自分で証明し、後でそれらを削除して、テストの足場でテストが煩雑にならないようにします。時々、足場を残すことはテストが物語として役立つのを助けます。


1

私はすでにこのテクニックについて読んだことがあります-おそらくあなたから-しかし、私はそれを使用しません。主に、ユニットテストのトリプルAフォームに慣れているためです。

今、私は興味があります、そしていくつかの質問があります:テストをどのように記述しますか?あなたはこのアサーションを失敗させますか?

おそらくコードをリファクタリングした後で失敗することがありますか?これは何を教えてくれますか?おそらく、それが役に立った例を共有できます。ありがとう。


私は通常、最初のアサーションを強制的に失敗させません-結局のところ、TDDアサーションがそうであるように、メソッドが記述される前に失敗するべきではありません。私それを書くとき、それを書くに、テストを書く通常の過程で、後ではありません。正直なところ、失敗したことは思い出せません。おそらくそれは時間の無駄だと思われます。例を考えてみますが、現時点では考えていません。質問ありがとうございます。彼らは役に立ちます。
Carl Manaster、2009年

1

失敗したテストを調査するとき、これを以前に行いました。

かなり頭をひっかいた後、原因は「アレンジ」中に呼び出されたメソッドが正しく機能していなかったことが原因であると判断しました。テストの失敗は誤解を招くものでした。アレンジ後にアサートを追加しました。これにより、実際の問題を強調する場所でテストが失敗しました。

テストのアレンジ部分が長すぎて複雑な場合、コードのにおいもここにあると思います。


マイナーなポイント:コードの匂いよりも複雑すぎるアレンジの方がデザインの匂いが多いと考えます。時々、複雑なアレンジだけでユニットをテストできるようなデザインになっていることがあります。その状況は、単純なコードの匂いよりも深い修正が必要なためです。
Carl Manaster、2010

1

一般的に、「アレンジ、アクト、アサート」はとても好きで、個人的な基準として使用しています。ただし、ここで忘れがちなことの1つは、アサーションが行われたときに私がアレンジしたことをアレンジしないことです。ほとんどの場合、ガベージコレクションなどを介してほとんどのことが自動的になくなるので、これはそれほど煩わしさを引き起こしません。ただし、外部リソースへの接続を確立している場合は、完了したらそれらの接続を閉じることをお勧めします。アサーションを使用するか、多くの場合、サーバーまたは高価なリソースがどこかにあり、他の誰かに渡すことができるはずの接続または重要なリソースを保持しています。これは、TearDownやTestFixtureTearDownを使用しない開発者の1人である場合に特に重要です。1つ以上のテスト後にクリーンアップする。もちろん、「アレンジ、アクト、アサート」は、私が開いたものを閉じなかった私の責任ではありません。「dispose」が推奨する適切な「A-word」同義語がまだ見つからないため、この「gotcha」についてのみ言及します。助言がありますか?


1
@carlmanaster、あなたは実際に私にとって十分に近いです!私は次のTestFixtureでそれを試してサイズを試してみます。それはあなたの母親があなたに教えるべきであったことをするための少しのリマインダーのようです:「それを開くならば、それを閉じてください!多分誰かがそれを改善することができますが、少なくともそれは「a!」で始まります あなたの提案をありがとう!
John Tobler、2011

1
@carlmanaster、私は「アヌル」を試してみました。「ティアダウン」よりはましだし、一種の作品ですが、「アレンジ、アクト、アサート」と同じくらい完全に頭に残る別の「A」の単語を探しています。たぶん「消滅?!」
ジョンTOBLER

1
だから今、私は「アレンジ、仮定、行動、アサート、全滅」を持っています。うーん!複雑すぎますね たぶん私はKISSをオフにして、「アレンジ、アクト、アサート!」に戻る方がよいでしょう。
ジョンTOBLER

1
リセットにRを使用することはできますか?私はそれがAではないことを知っていますが、海賊のように聞こえます:Aaargh!そしてアサートと韻をリセットします。o
マルセル・バルデスオロスコ

1

WikipediaのDesign by Contractに関する記事をご覧ください。アレンジアクトアサートホーリートリニティは、同じ概念のいくつかをエンコードする試みであり、プログラムの正確さを証明することを目的としています。記事から:

The notion of a contract extends down to the method/procedure level; the
contract for each method will normally contain the following pieces of
information:

    Acceptable and unacceptable input values or types, and their meanings
    Return values or types, and their meanings
    Error and exception condition values or types that can occur, and their meanings
    Side effects
    Preconditions
    Postconditions
    Invariants
    (more rarely) Performance guarantees, e.g. for time or space used

これを設定するのに費やされた労力とそれが付加する価値の間にはトレードオフがあります。AAAは必要な最小限の手順を思い出させるのに役立ちますが、追加の手順を作成することをだれにも妨げるものではありません。


0

テスト環境/言語に依存しますが、通常、Arrangeパーツの何かが失敗した場合、例外がスローされ、Actパーツを開始する代わりに、テストはそれを表示できません。したがって、通常、2つ目のアサートパーツは使用しません。

また、Arrangeパーツが非常に複雑で、常に例外がスローされるわけではない場合は、何らかの方法でラップして独自のテストを作成することを検討してください。失敗しないことを確認できます(例外をスローします)。


0

次のようなことをしていると思うので、このパターンは使用しません。

Arrange
Assert-Not
Act
Assert

アレンジパーツが正しく動作していることがわかっているため、アレンジパーツのすべてをテストするか、テストを必要としないほど単純にする必要があることを意味しているため、無意味かもしれません。

あなたの答えの例を使う:

public void testEncompass() throws Exception {
    Range range = new Range(0, 5);
    assertFalse(range.includes(7)); // <-- Pointless and against DRY if there 
                                    // are unit tests for Range(int, int)
    range.encompass(7);
    assertTrue(range.includes(7));
}

あなたは私の質問を本当に理解していません。最初のアサートはアレンジのテストに関するものではありません。それは単に、法律が最終的に主張される国家をもたらすものであることを保証することです。
Carl Manaster、2012

そして、私の主張は、アサート部分ではなく、アサート部分のコードは完全にテストされており、その機能がわかっているためです。
Marcel Valdez Orozco

しかし、あなたが言っているので、アサートではない部分に価値があると思います:アレンジ部分が「世界」を「この状態」のままにすることを考えると、私の「行為」は「世界」をこの「新しい状態」のままにします; アレンジ部分が依存するコードの実装が変更されると、テストも失敗します。しかし、繰り返しになりますが、アレンジ部分で依存しているコードのテストも行う必要があるので、DRYに反する可能性があります。
Marcel Valdez Orozco 2012

たぶん、同じプロジェクトに取り組んでいる複数のチーム(または大きなチーム)がいるプロジェクトでは、そのような句はかなり役に立ちます。そうでなければ、それは不必要で冗長であると思います。
マルセルバルデスオロスコ2012

おそらく、このような句は、統合テスト、システムテスト、または受け入れテストでより適切です。アレンジパーツは通常、複数のコンポーネントに依存し、「世界」の初期状態を予期せず変化させる可能性のある要因が他にもあります。しかし、単体テストでその場所がわかりません。
Marcel Valdez Orozco

0

例のすべてを本当にテストしたい場合は、次のようなテストを試してください。

public void testIncludes7() throws Exception {
    Range range = new Range(0, 5);
    assertFalse(range.includes(7));
}

public void testIncludes5() throws Exception {
    Range range = new Range(0, 5);
    assertTrue(range.includes(5));
}

public void testIncludes0() throws Exception {
    Range range = new Range(0, 5);
    assertTrue(range.includes(0));
}

public void testEncompassInc7() throws Exception {
    Range range = new Range(0, 5);
    range.encompass(7);
    assertTrue(range.includes(7));
}

public void testEncompassInc5() throws Exception {
    Range range = new Range(0, 5);
    range.encompass(7);
    assertTrue(range.includes(5));
}

public void testEncompassInc0() throws Exception {
    Range range = new Range(0, 5);
    range.encompass(7);
    assertTrue(range.includes(0));
}

そうでないと、エラーの可能性が非常に多く失われるためです。たとえば、範囲の後、範囲には7のみが含まれます。範囲の長さのテストもあります(ランダムな値も含まれていないことを確認するため)。範囲内に5を含めることを完全に試みるための別の一連のテスト...何が期待されますか-範囲内の例外、または範囲は変更されませんか?

とにかく、ポイントはあなたがテストしたい行為に何らかの仮定がある場合、それらを独自のテストに入れます、そうですか?


0

私が使う:

1. Setup
2. Act
3. Assert 
4. Teardown

クリーンなセットアップが非常に重要だからです。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.