Рефакторинг AutoFixture

Я начал использовать AutoFixture http://autofixture.codeplex.com/ в качестве своих модульных тестов, был чрезмерно увеличен в размере с большой установкой данных. Я проводил больше времени при установке данных, чем записать мой модульный тест. Вот пример того, как мой начальный модульный тест похож (пример, взятый от грузового образца приложения из синей книги DDD)

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

    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();
    var arrivalLoc = fixture.CreateAnonymous();
    var departureDateTime = fixture.CreateAnonymous();
    var arrivalDateTime = fixture.CreateAnonymous();

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

    var carrierMovements = fixture.CreateMany(50).ToList();

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

    var schedule = fixture.CreateAnonymous();

    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)));
}

Я хотел бы знать, существует ли лучший способ осуществить рефакторинг его. Хотел бы сделать это короче и легче, чем это.

10
задан John Saunders 12 April 2010 в 14:06
поделиться

1 ответ

Ваша первая попытка выглядит неплохо, но есть по крайней мере пару вещей, которые вы можете немного упростить.

Прежде всего, вы должны иметь возможность сократить это:

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 для использования тех же четырех значений.Хотя каждый созданный CarrierMovement будет отдельным экземпляром, все они будут иметь одни и те же четыре значения, и мне интересно, было ли это то, что вы имели в виду?

В том же ключе, что и выше, вместо

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

вы можете написать

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

поскольку вы не используете переменную carrierM . При определении типа будет выяснено, что вы регистрируете Schedule из-за типа возвращаемого значения Func.

Однако, если предположить, что конструктор Schedule выглядит следующим образом:

public Schedule(IEnumerable<CarrierMovement> carrierMovements)

вы могли бы вместо этого зарегистрировать carrierMovements следующим образом:

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

, что заставит AutoFixture автоматически разрешить расписание правильно. Этот подход более удобен в обслуживании, поскольку он позволяет добавлять параметр в конструктор расписания в будущем без нарушения теста (если AutoFixture может разрешить тип параметра).

Однако в этом случае мы можем добиться большего, потому что на самом деле мы не используем переменную carrierMovements ни для чего другого, кроме регистрации. Что нам действительно нужно сделать, так это просто указать AutoFixture, как создавать экземпляры IEnumerable . Если вас не волнует число 50 (вы не должны), мы можем даже использовать синтаксис группы методов следующим образом:

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

Обратите внимание на отсутствие скобок для вызова методов: мы регистрируем Func, и поскольку Метод CreateMany возвращает IEnumerable вывод типа берет на себя все остальное.

Однако это все детали. На более высоком уровне вы можете вообще не регистрировать CarrierMovement.Предполагая, что этот конструктор:

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

autofixture должен иметь возможность вычислить это самостоятельно.

Будет создан новый экземпляр Location для каждого отправленияLocation и прибытия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 , вам необходимо зарегистрировать его. Вот один из способов сделать это:

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

Однако, поскольку вы спрашиваете, я подразумеваю, что конструктор Schedule выглядит так:

public Schedule(IList<CarrierMovement> carrierMovements)

и я действительно думаю, что вам следует пересмотреть изменение этого API, чтобы он принимал IEnumerable . С точки зрения дизайна API предоставление коллекции через любой член (включая конструктор) подразумевает, что члену разрешено изменять коллекцию (например, вызывая его методы Add, Remove и Clear). Вряд ли такое поведение можно ожидать от конструктора, поэтому не допускайте этого.

AutoFixture автоматически сгенерирует новые значения для всех объектов Location в моем примере выше, но из-за скорости процессора последующие экземпляры DateTime, вероятно, будут идентичными.

Если вы хотите увеличить DateTimes, вы можете написать небольшой класс, который увеличивает возвращаемое DateTime каждый раз при его вызове.Я оставлю реализацию этого класса заинтересованному читателю, но затем вы можете зарегистрировать его следующим образом:

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

, предполагая этот API (еще раз обратите внимание на синтаксис группы методов выше):

public class DateTimeGenerator
{
    public DateTime Next();
}
14
ответ дан 3 December 2019 в 23:12
поделиться
Другие вопросы по тегам:

Похожие вопросы: