10

ユニットテストが大量のデータ設定で肥大化したため、 AutoFixturehttp : //autofixture.codeplex.com/を使い始めました。単体テストを作成するよりも、データの設定に多くの時間を費やしていました。これが私の最初のユニットテストがどのように見えるかの例です(DDDブルーブックの貨物アプリケーションサンプルから取られた例)

[Test]
public void should_create_instance_with_correct_ctor_parameters()
{
    var carrierMovements = new List<CarrierMovement>();

    var deparureUnLocode1 = new UnLocode("AB44D");
    var departureLocation1 = new Location(deparureUnLocode1, "HAMBOURG");
    var arrivalUnLocode1 = new UnLocode("XX44D");
    var arrivalLocation1 = new Location(arrivalUnLocode1, "TUNIS");
    var departureDate1 = new DateTime(2010, 3, 15);
    var arrivalDate1 = new DateTime(2010, 5, 12);

    var carrierMovement1 = new CarrierMovement(departureLocation1, arrivalLocation1, departureDate1, arrivalDate1);

    var deparureUnLocode2 = new UnLocode("CXRET");
    var departureLocation2 = new Location(deparureUnLocode2, "GDANSK");
    var arrivalUnLocode2 = new UnLocode("ZEZD4");
    var arrivalLocation2 = new Location(arrivalUnLocode2, "LE HAVRE");
    var departureDate2 = new DateTime(2010, 3, 18);
    var arrivalDate2 = new DateTime(2010, 3, 31);

    var carrierMovement2 = new CarrierMovement(departureLocation2, arrivalLocation2, departureDate2, arrivalDate2);

    carrierMovements.Add(carrierMovement1);
    carrierMovements.Add(carrierMovement2);

    new Schedule(carrierMovements).ShouldNotBeNull();
}

AutoFixtureでリファクタリングしようとした方法は次のとおりです

[Test]
public void should_create_instance_with_correct_ctor_parameters_AutoFixture()
{
    var fixture = new Fixture();

    fixture.Register(() => new UnLocode(UnLocodeString()));

    var departureLoc = fixture.CreateAnonymous<Location>();
    var arrivalLoc = fixture.CreateAnonymous<Location>();
    var departureDateTime = fixture.CreateAnonymous<DateTime>();
    var arrivalDateTime = fixture.CreateAnonymous<DateTime>();

    fixture.Register<Location, Location, DateTime, DateTime, CarrierMovement>(
        (departure, arrival, departureTime, arrivalTime) => new CarrierMovement(departureLoc, arrivalLoc, departureDateTime, arrivalDateTime));

    var carrierMovements = fixture.CreateMany<CarrierMovement>(50).ToList();

    fixture.Register<List<CarrierMovement>, Schedule>((carrierM) => new Schedule(carrierMovements));

    var schedule = fixture.CreateAnonymous<Schedule>();

    schedule.ShouldNotBeNull();
}

private static string UnLocodeString()
{
    var stringBuilder = new StringBuilder();

    for (int i = 0; i < 5; i++)
        stringBuilder.Append(GetRandomUpperCaseCharacter(i));

    return stringBuilder.ToString();
}

private static char GetRandomUpperCaseCharacter(int seed)
{
    return ((char)((short)'A' + new Random(seed).Next(26)));
}

それをリファクタリングするより良い方法があるかどうか知りたいです。それよりも短くて簡単にやりたいです。

4

1 に答える 1

14

最初の試みはうまくいっているように見えますが、少し単純化できることが少なくともいくつかあります。

まず第一に、これを減らすことができるはずです:

fixture.Register<Location, Location, DateTime, DateTime, CarrierMovement>(
    (departure, arrival, departureTime, arrivalTime) =>
        new CarrierMovement(departureLoc, arrivalLoc, departureDateTime, arrivalDateTime));

これに:

fixture.Register<Location, Location, DateTime, DateTime, CarrierMovement>(
    () => new CarrierMovement(departureLoc, arrivalLoc, departureDateTime, arrivalDateTime));

これらの他の変数を使用していないためです。ただし、これは本質的に CarrierMovement の作成をロックして、同じ 4 つの値を使用します。作成された各 CarrierMovement は個別のインスタンスになりますが、それらはすべて同じ 4 つの値を共有します。

上記と同じように、代わりに

fixture.Register<List<CarrierMovement>, Schedule>((carrierM) =>
    new Schedule(carrierMovements));

あなたは書ける

fixture.Register(() => new Schedule(carrierMovements));

carrierM変数を使用していないためです。型推論は、Func の戻り値の型により、Schedule を登録していることを認識します。

ただし、Schedule コンストラクターが次のようになっていると仮定します。

public Schedule(IEnumerable<CarrierMovement> carrierMovements)

carrierMovements代わりに、次のように登録することもできます。

fixture.Register<IEnumerable<CarrierMovement>>(carrierMovements);

これにより、AutoFixture が自動的にスケジュールを正しく解決します。このアプローチは、テストを中断することなく (AutoFixture がパラメーターの型を解決できる限り) 将来パラメーターを Schedule コンストラクターに追加できるため、保守性が高くなります。

carrierMovementsただし、この場合は、登録以外の目的で変数を実際に使用しないため、それよりもうまくいく可能性があります。本当に必要なのは、AutoFixture に のインスタンスを作成する方法を指示することだけですIEnumerable<CarrierMovement>。50 という数字を気にしない場合 (気にする必要はありません)、次のようなメソッド グループ構文を使用することもできます。

fixture.Register(fixture.CreateMany<CarrierMovement>);

メソッド呼び出しの括弧がないことに注意してください。Func を登録していて、CreateMany<T>メソッドが型を返すためIEnumerable<T>、残りは型推論によって処理されます。

ただし、それらはすべて詳細です。より高いレベルでは、CarrierMovement をまったく登録しないことを検討することをお勧めします。このコンストラクタを仮定すると:

public CarrierMovement(Location departureLocation,
    Location arrivalLocation,
    DateTime departureTime,
    DateTime arrivalTime)

autofixture は、それ自体でそれを把握できるはずです。

これにより、departureLocation とarrivalLocation ごとに新しい Location インスタンスが作成されますが、これは元のテストで手動で行ったものと同じです。

時刻に関しては、デフォルトで AutoFixture は を使用しますDateTime.Now。これにより、少なくとも到着時刻が出発時刻より前になることはありません。ただし、それらは同一である可能性が非常に高いですが、それが問題になる場合は、いつでも自動インクリメント関数を登録できます。

これらの考慮事項を考慮して、代替案を次に示します。

public void should_create_instance_with_correct_ctor_parameters_AutoFixture()
{
    var fixture = new Fixture();

    fixture.Register(() => new UnLocode(UnLocodeString()));

    fixture.Register(fixture.CreateMany<CarrierMovement>);

    var schedule = fixture.CreateAnonymous<Schedule>();

    schedule.ShouldNotBeNull();
}

問題を解決するにはIList<CarrierMovement>、登録する必要があります。これを行う1つの方法は次のとおりです。

fixture.Register<IList<CarrierMovement>>(() =>
    fixture.CreateMany<CarrierMovement>().ToList());

ただし、お尋ねのとおり、Schedule コンストラクターは次のようになります。

public Schedule(IList<CarrierMovement> carrierMovements)

その API を変更してIEnumerable<Carriemovement>. API 設計の観点から、任意のメンバー (コンストラクターを含む) を介してコレクションを提供することは、メンバーがコレクションを変更できることを意味します (たとえば、コレクションの Add、Remove、および Clear メソッドを呼び出すことによって)。これは、コンストラクターに期待するような動作ではありません。許可しないでください。

上記の例では、 AutoFixture はすべてのオブジェクトに対して新しい値を自動的に生成しLocationますが、CPU の速度が原因で、DateTime の後続のインスタンスは同じになる可能性があります。

DateTime を増やしたい場合は、呼び出されるたびに返される DateTime を増やす小さなクラスを作成できます。そのクラスの実装は興味のある読者にお任せしますが、次のように登録できます。

var dtg = new DateTimeGenerator();
fixture.Register(dtg.Next);

この API を想定しています (上記のメソッド グループの構文にもう一度注意してください)。

public class DateTimeGenerator
{
    public DateTime Next();
}
于 2010-04-12T13:54:52.513 に答える