単一クラスの単体テスト用の単一または複数のファイル?


20

組織のガイドラインをまとめるための単体テストのベストプラクティスを調査する際、テストフィクスチャ(テストクラス)を分離するか、1つのクラスのすべてのテストを1つのファイルに保持する方が良いか便利かという問題に直面しました。

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

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

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

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


2
テストメソッド名が長すぎます。テストメソッドに複数のアサーションが含まれることを意味する場合でも、それらを単純化します。
バーナード

12
@Bernard:メソッドごとに複数のアサーションを持つことは悪い習慣と見なされますが、長いテストメソッド名は悪い習慣とは見なされません。通常、メソッド自体がテストしているものを名前自体に文書化します。例えばconstructor_nullSomeList()、setUserName_UserNameIsNotValid()など...
c_maker

4
@Bernard:私も長いテスト名を使用しています(そしてそこにのみ!)。私はそれらが大好きです、何が機能していなかったのかが非常に明確だからです(あなたの名前が良い選択なら;))
セバスチャンバウアー

すべてが同じ結果をテストする場合、複数のアサーションは悪い習慣ではありません。例えば。あなたは持っていないだろうtestResponseContainsSuccessTrue()testResponseContainsMyData()testResponseStatusCodeIsOk()。あなたは、単一でそれらを持っているでしょうtestResponse()3がアサートしていますassertEquals(200, response.status)assertEquals({"data": "mydata"}, response.data)assertEquals(true, response.success)
ユハUntinen

回答:


18

まれですが、テスト対象の特定のクラスに対して複数のテストクラスを用意することが理にかなっています。通常、異なるセットアップが必要なときにこれを行い、テストのサブセット全体で共有します。


3
そもそもなぜそのアプローチが私に提示されたのかという良い例です。たとえば、CheckInメソッドをテストする場合、テスト対象のオブジェクトを常に1つの方法でセットアップする必要がありますが、CheckOutメソッドをテストする場合は、別のセットアップが必要です。いい視点ね。
SonOfPirate

14

単一のクラスのテストを複数のテストクラスに分割する説得力のある理由を実際に見ることはできません。駆動のアイデアはクラスレベルで結束を維持することであるため、テストレベルでも結束を維持する必要があります。いくつかのランダムな理由:

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

良い点。テスト対象のクラスがひどいクラッジである場合、ヘルパーロジックが含まれているいくつかのテストクラスを除外しません。私は非常に厳格なルールが好きではありません。それ以外は素晴らしい点です。
ジョブ

1
単一のクラスに対して機能するテストフィクスチャの共通の基本クラスを持つことで、重複するセットアップコードを排除します。私は他の2つの点にまったく同意しません。
SonOfPirate

たとえば、JUnitでは、さまざまなクラスを必要とするさまざまなランナーを使用してテストすることができます。
ヨアヒムニルソン

それぞれに複雑な数学を使用した多数のメソッドを持つクラスを作成すると、85個のテストがあり、これまでにメソッドの24%のテストしか作成していません。私がここにいるのは、サブセットを実行できるように、この膨大な数のテストを何らかの方法で別のカテゴリに分岐するのが正気かどうか疑問に思っていたからです。
Mooingダック

7

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

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


残念ながら、現実の世界ではすべてのクラスがSRPなどに準拠しているわけではありません。これは、レガシーコードのユニットテストを行うときに問題になります。
ペテルトレック

3
ここでSRPがどのように関連しているかはわかりません。クラスには複数のメソッドがあり、それらの動作を駆動するさまざまな要件のすべてについて単体テストを作成する必要があります。50個のテストメソッドを持つ1つのテストクラス、またはテスト中のクラスの特定のメソッドにそれぞれ関連する10個のテストを持つ5つのテストクラスを持つ方が良いでしょうか?
SonOfPirate

2
@SonOfPirate:非常に多くのテストが必要な場合は、メソッドの実行が多すぎることを意味する場合があります。したがって、SRPはここで非常に重要です。メソッドだけでなくクラスにもSRPを適用すると、テストしやすい小さなクラスとメソッドが多数あり、それほど多くのテストメソッドは必要ありません。(ただし、PéterTörökに同意します。レガシーコードをテストするときは、優れた実践が出ており、与えられたものを
最大限に活用

私が挙げることができる最も良い例は、オブジェクトがチェックアウトされているか、変更されているかなど、オブジェクトがチェックインする適切な状態にある場合、誰がアクション(許可)を実行できるかを決定するビジネスルールがあるCheckInメソッドです作ったことがある。オブジェクトがチェックアウトされていない、変更されていないなどのときにCheckInが呼び出された場合、メソッドが正しく動作することを確認するテストと同様に、承認規則が実施されていることを確認する単体テストがあります。テスト。これが間違っていることを提案していますか?
SonOfPirate

私は、この主題について、難しくて速く、正しいか間違った答えがあるとは思わない。あなたがやっていることがあなたのために働いているなら、それは素晴らしいことです。これは、一般的なガイドラインに関する私の見解です。
FishBasketGordo

2

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

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


1

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

このアプローチが元のシナリオにどのように適用されるかの例を次に示します。

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

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

私が活用することを学んだこのアプローチのもう1つの利点は、この構造のオブジェクト指向の性質です含まれるテストのネストされたクラス。


1

ちょっと考えてみてください。

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

単体テストは、特定のコンテキストで特定の動作をテストすることになっています。あなたのクラスがすでに持っているという事実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メソッドを直接テストしていません。代わりに動作をテストし(幸いにもチェックインすることです;))、リファクタリングする必要がある場合Documentして別のクラスに分割、または別のクラスをマージ、テストファイルは一貫性があり、リファクタリング時にロジックを変更することはなく、構造のみを変更します。

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


0

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

通常、実稼働クラスごとに1つの単体テストクラスから始めますが、最終的には、その単体テストクラスを、テストする動作に基づいて複数のテストクラスに分割する場合があります。つまり、テストクラスをCheckInShouldとCheckOutShouldに分割するのではなく、テスト対象のユニットの動作によって分割することをお勧めします。


0

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

最初の開発中、あなたは何をしているのかをかなりよく知っているので、両方のソリューションはおそらくうまくいくでしょう。

変更がずっと後にテストが失敗すると、さらに興味深いものになります。次の2つの可能性があります。

  1. あなたは真剣に壊れていますCheckIn(またはCheckOut)。この場合も、単一ファイルと2ファイルの両方のソリューションで問題ありません。
  2. CheckInand CheckOut(およびおそらくテスト)の両方を修正しましたが、それらの両方に対しては意味がありますが、両方を一緒にしたわけではありません。ペアの一貫性を壊しました。その場合、テストを2つのファイルに分割すると、問題を理解しにくくなります。

2.使用されるテストの検討

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

  1. プログラムを自動的にチェックすることはまだ正しく機能します。テストが1つのファイルにあるか、複数のファイルにあるかは、この目的には関係ありません。
  2. 人間の読者がプログラムを理解するのを助けてください。密接に関連する機能のテストをまとめておけば、これははるかに簡単になります。それらの一貫性を見ることは理解への迅速な道だからです。

そう?

これらの両方の観点から、テストをまとめておくと役立つことがありますが、害はありません。

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