ユニットテストを使用してストーリーを伝えるのは良い考えですか?


13

だから、私は少し前に書いた認証モジュールを持っています。今、私は自分のやり方のエラーを見、それのための単体テストを書いています。単体テストを書いている間、私は良い名前とテストする良い領域を思いつくのに苦労しています。例えば、私は次のようなものを持っています

  • RequiresLogin_should_redirect_when_not_logged_in
  • RequiresLogin_should_pass_through_when_logged_in
  • Login_should_work_when_given_proper_credentials

個人的には、「適切」に見えても、少しthoughいと思います。また、テストをスキャンするだけでテストを区別するのに苦労しています(失敗したものを知るには、メソッド名を少なくとも2回読む必要があります)

だから、機能を純粋にテストするテストを書く代わりに、シナリオをカバーするテストのセットを書くかもしれないと思った。

たとえば、これは私が思いついたテストスタブです:

public class Authentication_Bill
{
    public void Bill_has_no_account() 
    { //assert username "bill" not in UserStore
    }
    public void Bill_attempts_to_post_comment_but_is_redirected_to_login()
    { //Calls RequiredLogin and should redirect to login page
    }
    public void Bill_creates_account()
    { //pretend the login page doubled as registration and he made an account. Add the account here
    }
    public void Bill_logs_in_with_new_account()
    { //Login("bill", "password"). Assert not redirected to login page
    }
    public void Bill_can_now_post_comment()
    { //Calls RequiredLogin, but should not kill request or redirect to login page
    }
}

これはパターンを聞いたことがありますか?私は受け入れの物語などを見てきましたが、これは根本的に異なります。大きな違いは、テストを「強制」するシナリオを考えていることです。手動でテストする必要がある可能性のある相互作用を考え出すのではなく。また、これにより、1つのメソッドとクラスを正確にテストしない単体テストが奨励されることを知っています。これは大丈夫だと思います。また、これは少なくともいくつかのテストフレームワークで問題が発生することを認識しています。通常、テストは相互に独立しており、順序は重要ではありません(この場合はどこか)。

とにかく、これはお勧めのパターンですか?または、これは「ユニット」テストとしてではなく、APIの統合テストに最適ですか?これは単なる個人的なプロジェクトなので、うまくいくかもしれないし、うまくいかないかもしれません。


4
ユニットテスト、統合テスト、機能テストの間の線はあいまいです。テストスタブの名前選択する必要がある場合、機能します。
ヤンニス

それは好みの問題だと思います。個人的には、テストを_test追加した名前を使用し、コメントを使用して期待する結果を記録します。個人的なプロジェクトの場合は、快適に感じるスタイルを見つけ、それに固執します。
リスター氏

1
Arrange / Act / Assertパターンを使用した従来の単体テストの記述方法について詳細に回答を書きましたが、友人はgithub.com/cucumber/cucumber/wiki/Gherkinを使用して多くの成功を収めています。仕様とafaikに使用すると、キュウリのテストを生成できます。
StuperUser

nunitなどで示した方法は使用しませんが、nspecは、よりストーリー主導型のコンテキストとテストの構築をサポートしています。nspec.org
Mike

1
変更「ユーザー」を「ビル」とすれば完了です
スティーブンA. Loweの

回答:


15

はい、テストするシナリオ例の名前をテストに付けることをお勧めします。そして、単体テスト以上の単体テストツールを使用しても大丈夫かもしれませんが、多くの人がこれを成功させています(私も)。

しかし、いや、テストの実行順序が重要になるような方法でテストを書くことは絶対に良い考えではありません。たとえば、NUnitを使用すると、ユーザーは実行したいテストをインタラクティブに選択できるため、これは意図したとおりに機能しなくなります。

ここでは、各テストの主要なテスト部分(「アサート」を含む)をシステムを正しい初期状態に設定する部分から分離することにより、これを簡単に回避できます。上記の例を使用して:アカウントを作成し、ログオンしてコメントを投稿するためのメソッドを作成します-アサートなし。次に、これらのメソッドをさまざまなテストで再利用します。また[Setup]、テストフィクスチャのメソッドにコードを追加して、システムが適切に定義された初期状態にあることを確認する必要があります(たとえば、データベースにアカウントがこれまでにない、接続されているユーザーがいないなど)。

編集:もちろん、これはテストの「ストーリー」性に反しているようですが、ヘルパーメソッドに意味のある名前を付けると、各テスト内でストーリーを見つけることができます。

したがって、次のようになります。

[TestFixture]
public class Authentication_Bill
{
    [Setup]
    public void Init()
    {  // bring the system in a predefined state, with noone logged in so far
    }

    [Test]
    public void Test_if_Bill_can_create_account()
    {
         CreateAccountForBill();
         // assert that the account was created properly 
    }

    [Test]
    public void Test_if_Bill_can_post_comment_after_login()
    { 
         // here is the "story" now
         CreateAccountForBill();
         LoginWithBillsAccount();
         AddCommentForBill();
        //  assert that the right things happened
    }

    private void CreateAccountForBill()
    {
        // ...
    }
    // ...
}

さらに進んで、xUnitツールを使用して機能テストを実行しても、ツールとテストの種類を混同しない限り、そしてこれらのテストを実際の単体テストから分離し、開発者ができる限りコミット時にユニットテストをすばやく実行します。これらは、単体テストよりもはるかに遅い可能性があります。
-bdsl

4

単体テストでストーリーを伝える際の問題は、単体テストを互いに完全に独立して配置および実行する必要があることを明示していないことです。

優れたユニットテストは、他のすべての依存コードから完全に分離する必要があります。これは、テストできるコードの最小ユニットです。

これにより、コードが機能することを確認するだけでなく、テストが失敗した場合にコードが間違っている場所を正確に診断できるという利点があります。テストが分離されていない場合は、何が間違っているかを正確に見つけ、ユニットテストの大きな利点を逃すために、それが依存するものを見なければなりません。実行順序の問題も多くの偽陰性を引き起こす可能性があります。テストが失敗すると、テストするコードが完全に正常に機能しているにもかかわらず、次のテストが失敗する可能性があります。

より詳細な良い記事は、ダーティハイブリッドテストの古典です。

クラス、メソッド、結果を読みやすくするために、優れた単体テストでは命名規則を使用しています

テストクラス:

ClassUnderTestTests

試験方法:

MethodUnderTest_Condition_ExpectedResult

@Doc Brownの例をコピーするには、各テストの前に実行される[セットアップ]を使用するのではなく、テストする分離オブジェクトを構築するヘルパーメソッドを記述します。

[TestFixture]
public class AuthenticationTests
{
    private Authentication GetAuthenticationUnderTest()
    {
        // create an isolated Authentication object ready for test
    }

    [Test]
    public void CreateAccount_WithValidCredentials_CreatesAccount()
    {
         //Arrange
         Authentication codeUnderTest = GetAuthenticationUnderTest();
         //Act
         Account result = codeUnderTest.CreateAccount("some", "valid", "data");
         //Assert
         //some assert
    }

    [Test]
    public void CreateAccount_WithInvalidCredentials_ThrowsException()
    {
         //Arrange
         Authentication codeUnderTest = GetAuthenticationUnderTest();
         Exception result;
         //Act
         try
         {
             codeUnderTest.CreateAccount("some", "invalid", "data");
         }
         catch(Exception e)
         {
             result = e;
         }
         //Assert
         //some assert
    }
}

したがって、失敗したテストには意味のある名前が付けられ、失敗したメソッド、条件、および期待される結果を正確に説明できます。

それが私が常に単体テストを書いてきた方法ですが、友人はGerkinで多くの成功を収めています


1
これは良い投稿だと思いますが、リンクされた記事が「ハイブリッド」テストについて何と言っているかについては同意しません。「小さな」統合テスト(もちろん、純粋な単体テストに代わるものではありません)があると、どのメソッドに間違ったコードが含まれているかを正確に伝えることができない場合でも、IMHOは非常に役立ちます。それらのテストが保守可能かどうかが、それらのテストのコードがどれだけきれいに書かれているかに依存する場合、それ自体は「ダーティ」ではありません。そして、これらのテストの目標は非常に明確になると思います(OPの例のように)。
ドックブラウン

3

あなたが説明していることは、私にとって単体テストというよりも、行動駆動設計(BDD)に似ています。Gherkin DSLに基づいた.NET BDD テクノロジであるSpecFlowご覧ください。

人間がコーディングについて何も知らなくても読み書きできる強力なもの。私たちのテストチームは、統合テストスイートにそれを活用して大成功を収めています。

単体テストの規則に関して、@ DocBrownの答えは堅実に思えます。


情報については、BDDはTDDとまったく同じであり、変化するのは単なる書き方です。例:TDD = assert(value === expected)BDD = value.should.equals(expected)+「ユニットテストの独立性」問題を解決するレイヤーの機能を記述します。これは素晴らしいスタイルです!
オフィルモ
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.