2

私は単体テストの初心者です-単なるテストメソッドを使用して基本的なアサートテストしか行っていません(最後のモジュール、約50個作成しました)。

私は現在、単体テストに関する本を読んでいます。本にある多くの例の 1 つで、単一のテストごとに新しいクラスを作成しています。以下は、1 つのテスト ケース用に作成されたサンプル オブジェクトの 1 つです。私の質問は、これを行う必要があるのですか? または、いつこのアプローチを適用する必要があり、いつ必要でないのでしょうか?

  public class and_saving_an_invalid_item_type : when_working_with_the_item_type_repository
    {
        private Exception _result;

        protected override void Establish_context()
        {
            base.Establish_context();

            _session.Setup(s => s.Save(null)).Throws(new ArgumentNullException());
        }

        protected override void Because_of()
        {
            try
            {
                _itemTypeRepository.Save(null);
            }
            catch (Exception exception)
            {
                _result = exception;
            }
        }

        [Test]
        public void then_an_argument_null_exception_should_be_raised()
        {
            _result.ShouldBeInstanceOfType(typeof(ArgumentNullException));
        }
    }
4

2 に答える 2

3

個々のテストごとに新しいクラスを作成する必要がありますか? いいえ、あなたは確かにそうではありません。なぜ本がそう言っているのか、それとも例を説明するためにそうしているのかはわかりません.

あなたの質問に答えるために、テストのグループごとにクラスを使用することをお勧めします...しかし、「グループ」を定義する方法はさまざまであり、その時点で何をしているかに依存するため、実際にはそれよりも少し複雑です.

私の経験では、一連のテストは実際にはドキュメントのように論理的に構造化されており、いくつかの共通の側面によってグループ化された (場合によってはネストされた) 1 つまたは複数のテスト セットを含めることができます。オブジェクト指向コードをテストするための自然なグループ化は、クラスごとにグループ化し、次にメソッドごとにグループ化することです。

これが例です

  • クラス1のテスト
    • 方法 1 のテスト
      • メソッド 1 の主な動作
      • 方法 1 の代替動作
    • 方法 2 のテスト
      • メソッド 2 の主な動作
      • 方法 2 の代替動作

残念ながら、C# や Java (または同様の言語) では、(実際に必要な 3 つまたは 4 つのレベルとは対照的に) 操作する構造が 2 つのレベルしかないため、適合するようにハックする必要があります。

これを行う一般的な方法は、次のように、クラスを使用して一連のテストをグループ化し、メソッド レベルでは何もグループ化しないことです。

class TestsForClass1 {
  void Test_method1_primary()
  void Test_method1_alternate()

  void Test_method2_primary()
  void Test_method2_alternate()
}

方法 1 と方法 2 の両方で同じセットアップ/ティアダウンがある場合、これは問題ありませんが、そうでない場合があり、この内訳につながります。

class TestsForClass1_method1 {
  void Test_primary()
  void Test_alternate()
}

class TestsForClass1_method2 {
  void Test_primary()
  void Test_alternate()
}

より複雑な要件がある場合 (たとえば、method_1 に 10 個のテストがあり、最初の 5 つはセットアップ要件 X で、次の 5 つは異なるセットアップ要件です)、人々は通常、次のようなクラス名をますます多く作成することになります:

class TestsForClass1_method1_withRequirementX {  ... }
class TestsForClass1_method1_withRequirementY {  ... }

これはひどいですが、ねえ-四角いペグ、丸い穴など。

個人的には、メソッド内でラムダ関数を使用して第 3 レベルのグループ化を行うのが好きです。NSpecは、これを実行できる 1 つの方法を示しています...社内のテスト フレームワークはわずかに異なり、次のようになります。

class TestsForClass1 {
   void TestsForMethod1() {
      It.Should("perform it's primary function", () => {
         // ....
      });

      It.Should("perform it's alternate function", () => {
         // ....
      });
   }
}

これにはいくつかの欠点があります (最初の It ステートメントが失敗すると、他のステートメントは実行されません) が、このトレードオフは価値があると考えています)。


-- 当初の質問は、「実行したいテストごとにオブジェクトを作成することが本当に必要ですか?」というものでした。この説明によると、それに対する答えは (ほとんど) はいです。

通常、単体テストには 2 つの部分の相互作用が含まれます。

  • テスト対象のオブジェクト。通常、これは作成したクラスまたは関数のインスタンスです
  • 環境。通常、これは、関数に渡したパラメーターと、オブジェクトが参照するその他の依存関係です。

単体テストが信頼できるものであるためには、システムの状態が健全で信頼できるものであることを保証するために、これらの部分の両方が各テストで「新鮮」である必要があります。

  • テスト対象がテストごとに更新されない場合、1 つの関数がオブジェクトの内部状態を変更し、次のテストが誤って失敗する可能性があります。

  • テストごとに環境が更新されない場合、1 つの関数が環境を変更する可能性があり (例: 外部データベースなどに変数を設定する)、次のテストが誤って失敗する可能性があります。

これが当てはまらない状況が明らかに多くあります。たとえば、整数のみをパラメーターとして取り、外部状態に触れない純粋な数学関数がある場合、わざわざオブジェクトを再作成したくない場合があります。テストまたはテスト環境...しかし、一般的に、オブジェクト指向システムのほとんどのものは更新が必要になるため、これが「標準的な方法」です。

于 2012-06-04T23:27:11.920 に答える
2

私はあなたの例を完全にフォローすることはできませんが、理想的には、どのテストケースも他のものとは独立して実行できる必要があります。

于 2012-06-04T23:27:19.660 に答える