3

現在、各内にテスト対象のオブジェクトを作成して単体テストを作成していますTestMethod。これを行う主な理由は、自己完結型、読みやすさ/デバッグの容易さを促進し、構築中に依存関係を調整することです。

public class MyClass
{
    public MyClass(ISomeService serviceA, ISomeOtherService serviceB)
    {
        ...
    }
}

[TestMethod]
public void it_should_do_something()
{
    var myClass = new MyClass(serviceA, serviceB);
    myClass.DoSomething();
    ...
}

ただし、これはメンテナンスの悪夢になりつつあります。特に、クラスに多くのテストがあり、コンストラクターに別の依存関係を追加したい場合はそうです。テストクラスの構成の各行を変更する必要があります。

メソッドでオブジェクトを初期化することにより、物事をDRYに保つことを目指していますTestInitialize。これにより、自己完結型の側面を維持しながらメンテナンスが改善されます。

private MyClass _myClass;

[TestInitialize]
public void Init()
{
    _myClass = new MyClass(serviceA, serviceB);
}

これを行うことの唯一の欠点は、別の依存関係セットを注入する必要がある場合、オブジェクトを再初期化する必要があることです。また、コードを初めて見た別の開発者が読んだりデバッグしたりするのは、少しわかりにくいかもしれません。このアプローチには、私が考えていない他のマイナス点はありますか?

  1. このシナリオのベスト プラクティスは何ですか?

  2. TestMethod の外でクラスを初期化するのは悪い習慣ですか?

  3. メンテナンスと DRY の原則を支持するべきですか、それとも読みやすさ/論理的な目的のために物事を自己完結型に保つべきですか?

4

1 に答える 1

7

そのテストは、1) 読みやすく、2) 書きやすく、保守しやすいものでなければなりません。「読める」とは?これは、特定のコード行で何が行われているかを理解するためにコードをジャンプする量を最小限に抑えることを意味します。

テスト プロジェクトでファクトリを作成することを検討してください。これは、テストの配置を読みやすくするための最良の方法の 1 つだと思います。

public static class MyClassFactory
{
  public static MyClass Create(ISomeService serviceA, ISomeOtherService serviceB)
  {
    return new MyClass(serviceA, serviceB);
  }

  public static MyClass CreateEmpty()
  {
    return new MyClass();
  }

//Just an example, but in general not really recommended.
//May be acceptable for simple projects, but for large ones
//remembering what does "Default" mean is an unnecessary burden
//and it somehow reduces readability.
  public static MyClass CreateWithDefaultServices()      
  {
    return new MyClass(/*...*/);
  }
  //...
}

(2 番目のオプションについては後で説明します)。次に、次のように使用できます。

[TestMethod]
public void it_should_do_something()
{
    //Arrange
    var myClass = MyClassFactory.Create(serviceA, serviceB);

    //Act
    myClass.DoSomething();

    //Assert
    //...
}

読みやすさが向上することに注意してください。あなたの例に合わせるためだけに残したserviceAserviceBのほかに、理解する必要があるすべてがこの行にあります。理想的には、次のようになります。

[TestMethod]
public void it_should_do_something()
{
    //Arrange
    var myClass = MyClassFactory.Create(
        MyServiceFactory.CreateStubWithTemperature(45),
        MyOtherServiceFactory.CreateStubWithContents("test string"));

    //Act
    myClass.DoSomething();

    //Assert
    //...
}

これにより可読性が向上します。DRYが使用されていないセットアップであっても、すべてのファクトリ メソッドは単純で明白です。それらの定義を調べる必要はありません。一目で明らかです。

書きやすさと保守のしやすさをさらに向上させることはできますか?

MyClassクールなアイデアは、読み取り可能な構成を可能にするために、テスト プロジェクトに拡張メソッドを提供することです。

public static MyClassTestExtensions
{
  public static MyClass WithService(
      this MyClass @this,
      ISomeService service)
  {
    @this.SomeService = service; //or something like this
    return @this;
  }

  public static MyClass WithOtherService(
      this MyClass @this,
      ISomeOtherService service)
  {
    @this.SomeOtherService = service; //or something like this
    return @this;
  }
}

次に、もちろん、次のように使用します。

[TestMethod]
public void it_should_do_something()
{
    //Arrange
    var serviceA = MyServiceFactory.CreateStubWithTemperature(45);
    var serviceB = MyOtherServiceFactory.CreateStubWithContents("test string");
    var myClass = MyClassFactory
        .CreateEmpty()
        .WithService(serviceA)
        .WithOtherService(serviceB);

    //Act
    myClass.DoSomething();

    //Assert
    //...
}

もちろん、サービスはそのような構成の良い例ではありません (通常、サービスはそれらなしでは使用できないため、デフォルトのコンストラクターは提供されていません) が、他のプロパティには大きな利点があります: 構成する N 個のプロパティを使用すると、クリーンで読み取り可能なN x N ファクトリ メソッドの代わりに N 個の拡張メソッドを使用し、コンストラクタまたはファクトリ メソッドの呼び出しで N の長さの引数リストを使用せずに、可能なすべての構成でテスト オブジェクトを作成する方法。

クラスが上で想定したほど便利でない場合、考えられる解決策の 1 つはクラスをサブクラス化し、サブクラスのファクトリと拡張機能を定義することです。

オブジェクトがそのプロパティの一部だけで存在することが意味がなく、それらを設定できない場合は、ファクトリを流暢に構成してからオブジェクトを作成できます。

//serviceA and serviceB creation
var myClass = MyClassFactory
  .WithServiceA(serviceA)
  .WithServiceB(serviceB)
  .CreateMyClass();

そのようなファクトリのコードが推測しやすいことを願っているので、この回答はすでに少し長いように思われるため、ここには書きません ;)

于 2013-08-18T10:40:50.543 に答える