Pergunta

Comecei a usar autofixture http://autofixture.codeplex.com/ Como meus testes de unidade foram inchados com muita configuração de dados. Eu estava gastando mais tempo configurando os dados do que escrever meu teste de unidade. Aqui está um exemplo de como é o meu teste de unidade inicial (exemplo retirado da amostra de aplicação de carga do DDD Blue Book)

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

Veja como eu tentei refatorá -lo com 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)));
}

Eu gostaria de saber se há melhor maneira de refatorá -lo. Gostaria de fazer isso mais curto e mais fácil do que isso.

Foi útil?

Solução

Sua tentativa inicial parece boa, mas há pelo menos algumas coisas que você pode simplificar um pouco.

Primeiro de tudo, você deve ser capaz de reduzir isso:

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

para isso:

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

já que você não está usando essas outras variáveis. No entanto, isso bloqueia essencialmente qualquer criação de carrinho para usar os mesmos quatro valores. Embora cada um criador de CarrierMovement seja uma instância separada, todos compartilharão os mesmos quatro valores, e eu me pergunto se foi isso que você quis dizer?

Na mesma linha que acima, em vez de

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

você pode escrever

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

já que você não usa o carrierM variável. O tipo de infenderamento descobrirá que você está registrando um cronograma devido ao tipo de retorno da func.

No entanto, assumindo que o construtor de cronograma se pareça com o seguinte:

public Schedule(IEnumerable<CarrierMovement> carrierMovements)

você poderia ter acabado de registrar o carrierMovements assim:

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

O que causaria a AutoFixture para resolver automaticamente o cronograma corretamente. Essa abordagem é mais sustentável porque permite adicionar um parâmetro ao construtor de programação no futuro, sem quebrar o teste (desde que o AutoFixture possa resolver o tipo de parâmetro).

No entanto, podemos fazer melhor do que isso neste caso, porque realmente não usamos o carrierMovements variável para qualquer outra coisa além do registro. O que realmente precisamos fazer é apenas dizer a mistura automática de como criar instâncias de IEnumerable<CarrierMovement>. Se você não se importa com o número 50 (você não deveria), podemos até usar a sintaxe do grupo de métodos como esta:

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

Observe a falta de paranteses de invocação de métodos: estamos registrando uma func CreateMany<T> Método retorna IEnumerable<T> O inferno do tipo cuida do resto.

No entanto, esses são todos detalhes. Em um nível mais alto, você pode considerar não registrar o CarrierMovement. Supondo este construtor:

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

O Autofixture deve ser capaz de descobrir isso por si só.

Ele criará uma nova instância de localização para todas as departamentos e práticas, mas isso não é diferente do que você fez manualmente no teste original.

Quando se trata do tempo, por padrão o AutoFixture usa DateTime.Now, o que pelo menos garante que o horário de chegada nunca seja antes do horário de partida. No entanto, é muito provável que eles sejam idênticos, mas você sempre pode registrar uma função de incrementação automática, se isso for um problema.

Dadas essas considerações, aqui está uma alternativa:

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

Para resolver o problema com IList<CarrierMovement> Você precisará registrá -lo. Aqui está uma maneira de fazer isso:

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

No entanto, desde que você pergunta, eu sugero que o construtor de cronograma se parece com o seguinte:

public Schedule(IList<CarrierMovement> carrierMovements)

E eu realmente acho que você deveria reconsiderar a mudança dessa API para tomar um IEnumerable<Carriemovement>. Do ponto de vista do design da API, o fornecimento de uma coleção através de qualquer membro (incluindo um construtor) implica que o membro pode modificar a coleção (por exemplo, invocando seus métodos Adicionar, Remover e Limpar). Isso dificilmente é um comportamento que você esperaria de um construtor, então não permita.

Autofixture gerará automaticamente novos valores para todos Location Objetos no meu exemplo acima, mas devido à velocidade da CPU, é provável que as instâncias subseqüentes do DateTime sejam idênticas.

Se você deseja aumentar o DATET às vezes, pode escrever uma classe pequena que incrementa o DateTime retornado toda vez que for invocado. Vou deixar a implementação dessa classe para o leitor interessado, mas você pode registrá -la assim:

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

Assumindo esta API (observe mais uma vez a sintaxe do grupo de métodos acima):

public class DateTimeGenerator
{
    public DateTime Next();
}
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top