web-dev-qa-db-ja.com

単一のクラスを単体テストするための単一または複数のファイル?

組織のガイドラインをまとめるのに役立つユニットテストのベストプラクティスを調査する際に、テストフィクスチャ(テストクラス)を分離する方が良いのか、それとも1つのファイルのすべてのテストを1つのファイルに保存するのが良いのかという疑問に遭遇しました。

Fwiw、私は純粋な意味で「単体テスト」を参照しています。これらは、単一のクラス、テストごとに1つのアサーション、モックされたすべての依存関係などを対象とするホワイトボックステストです。

シナリオの例は、CheckInとCheckOutの2つのメソッドを持つクラス(Documentと呼びます)です。各メソッドは、その動作を制御するさまざまなルールなどを実装します。テストごとに1つのアサーションルールに従って、メソッドごとに複数のテストを行います。すべてのテストを、DocumentTestsCheckInShouldThrowExceptionWhenUserIsUnauthorizedのような名前の単一のCheckOutShouldThrowExceptionWhenUserIsUnauthorizedクラスに配置することもできます。

または、CheckInShouldCheckOutShouldの2つの個別のテストクラスを使用することもできます。この場合、私のテスト名は短縮されますが、特定の動作(メソッド)のすべてのテストが一緒になるように整理されます。

私はどちらのアプローチにも賛否両論があると確信しており、誰かが複数のファイルを使用してルートを進んだかどうか疑問に思っています。もしそうなら、なぜですか?または、単一ファイルのアプローチを選択した場合、なぜそれが優れていると感じますか?

20
SonOfPirate

私が採用することを選択した手法が最初に実証されたリンクを覚えている/見つけられるといいのですが。基本的に、テストする各メンバーのネストされたテストフィクスチャ(クラス)を含む、テスト中の各クラスに対して単一の抽象クラスを作成します。これにより、本来必要な分離が提供されますが、すべてのテストが場所ごとに同じファイルに保持されます。さらに、これにより、テストランナーで簡単にグループ化およびソートできるクラス名が生成されます。

このアプローチを私の元のシナリオに適用する方法の例を次に示します。

public abstract class DocumentTests : TestBase
{
    [TestClass()]
    public sealed class CheckInShould : DocumentTests
    {
        [TestMethod()]
        public void ThrowExceptionWhenUserIsNotAuthorized()
        {
        }
    }

    [TestClass()]
    public sealed class CheckOutShould : DocumentTests
    {
        [TestMethod()]
        public void ThrowExceptionWhenUserIsNotAuthorized()
        {
        }
    }
}

これにより、次のテストがテストリストに表示されます。

DocumentTests+CheckInShould.ThrowExceptionWhenUserIsNotAuthorized
DocumentTests+CheckOutShould.ThrowExceptionWhenUserIsNotAuthorized

テストが追加されると、それらは簡単にグループ化され、クラス名でソートされます。これにより、テスト中のクラスのすべてのテストが一緒にリストされます。

私が活用することを学んだこのアプローチの別の利点は、この構造のオブジェクト指向の性質であり、すべてのネストされたクラスとそれぞれの内部で共有されるDocumentTests基本クラスの起動メソッドとクリーンアップメソッドの内部でコードを定義できることを意味します含まれるテストのネストされたクラス。

1
SonOfPirate

まれですが、テスト対象の特定のクラスに対して複数のテストクラスを使用することは意味がある場合があります。通常、これは、異なるセットアップが必要で、テストのサブセット全体で共有される場合に行います。

18
Carl Manaster

単一のクラスのテストを複数のテストクラスに分割する説得力のある理由は本当にわかりません。原動力となるアイデアはクラスレベルでの一貫性を維持することであるため、テストレベルでも同様に努力する必要があります。いくつかのランダムな理由:

  1. セットアップコードを複製する(および複数のバージョンを維持する)必要がない
  2. IDEが1つのテストクラスにグループ化されている場合、クラスのすべてのテストを簡単に実行できます
  3. テストクラスとクラス間の1対1のマッピングにより、トラブルシューティングが簡単になります。
14
pap

クラスの単体テストを複数のファイルに分割せざるを得ない場合、それはクラス自体の設計が不十分であることを示している可能性があります。私は、単一責任の原則と他のプログラミングのベストプラクティスに合理的に準拠しているクラスの単体テストを分割する方が有益なシナリオは考えられません。

さらに、ユニットテストのメソッド名を長くすることも可能ですが、気になる場合は、ユニットテストの命名規則をいつでも見直して、名前を短くすることができます。

7
FishBasketGordo

テストを複数のクラスに分割することに反対する私の議論の1つは、チームの他の開発者(特にテストに精通していない開発者)が既存のテストを見つけるのが難しくなることです(このメソッドのテスト?どこにあるのだろう?)また、新しいテストをどこに置くか(このクラスでこのメソッドのテストを作成しますが、それらを新しいファイルまたは既存のファイルに入れるべきですか?

異なるテストで大幅に異なるセットアップが必要な場合、TDDの実践者が「フィクスチャ」または「セットアップ」を異なるクラス/ファイルに配置し、テスト自体は配置しないことを見てきました。

2
rally25rs

少し考え直してみてください。

クラスごとにテストクラスが1つしかないのはなぜですか。クラステストまたはユニットテストを行っていますか? classesにも依存していますか?

ユニットテストは、特定のコンテキストで特定の動作をテストすることになっています。クラスにCheckInがすでにあるということは、そもそもそれを必要とする動作があるはずであることを意味します。

この疑似コードについてどう思いますか:

// check_in_test.file

class CheckInTest extends TestCase {
        /** @test */
        public function unauthorized_users_cannot_check_in() {
                $this->expectException();

                $document = new Document($unauthorizedUser);

                $document->checkIn();
        }
}

ここでは、checkInメソッドを直接テストしていません。代わりにbehaviourをテストしています(幸いにも;)でチェックインします)、Documentをリ​​ファクタリングして別のクラスに分割するか、別のクラスをマージする必要がある場合、テストファイルはリファクタリング時にロジックを変更することはなく、構造のみを変更するため、一貫性があります。

1つのクラスに1つのテストファイルを使用すると、必要なときにいつでもリファクタリングが難しくなるだけでなく、コード自体のドメイン/ロジックの観点からもテストの意味が少なくなります。

1

1.何がうまくいかないかを検討する

最初の開発時には、自分が何をしているかをかなりよく理解しており、どちらのソリューションもおそらく問題なく動作します。

変更後のテストが失敗したときに、それはより興味深いものになります。 2つの可能性があります。

  1. CheckIn(またはCheckOut)を深刻に破壊しました。この場合も、単一ファイルと2ファイルのソリューションはどちらも問題ありません。
  2. CheckInCheckOutの両方(およびおそらくそれらのテスト)を、それぞれ一緒にではなく、それぞれに対して意味のある方法で変更しました。あなたはペアの一貫性を壊しました。その場合、テストを2つのファイルに分割すると、問題の理解が難しくなります。

2.どのようなテストが使用されるかを検討する

テストには、主に次の2つの目的があります。

  1. プログラムがまだ正常に機能していることを自動的に確認します。テストが1つのファイルにあるか複数のファイルにあるかは、この目的には関係ありません。
  2. 人間の読者がプログラムを理解できるようにします。密接に関連する機能のテストをまとめておくと、一貫性を確認してすぐに理解することができるため、これははるかに簡単です。

そう?

これらの両方の見方は、テストをまとめることが役立つことを示唆していますが、害はありません。

0
Lutz Prechelt

一般に、テストはメソッドではなくクラスの動作をテストすることと考えることをお勧めします。つまり一部のテストでは、予想される動作をテストするために、クラスの両方のメソッドを呼び出す必要がある場合があります。

通常、私はプロダクションクラスごとに1つの単体テストクラスから始めますが、最終的には、その単体テストクラスを、テストする動作に基づいていくつかのテストクラスに分割する可能性があります。言い換えれば、テストクラスをCheckInShouldとCheckOutShouldに分割するのではなく、テスト中のユニットの動作によって分割しないことをお勧めします。

0