Autofixture: Abordagem para gerar espécime aleatório com base na personalização

Criado em 8 jan. 2018  ·  11Comentários  ·  Fonte: AutoFixture/AutoFixture

Olá, quero ser capaz de gerar valores distintos com base em ICustomization usando ISpecimenBuilder.CreateMany . Eu queria saber qual seria a melhor solução, já que a AutoFixture irá gerar os mesmos valores para todas as entidades.

`` `c #
public class FooCustomization: ICustomization
{
public void Customize (IFixture fixture)
{
var specimen = fixture.Build()
.OmitAutoProperties ()
.Com (x => x.CreationDate, DateTime.Now)
.With (x => x.Identifier, Guid.NewGuid (). ToString (). Substring (0, 6)) // Deve gerar valores distintos
.Com (x => x.Mail, $ "[email protected]")
.Crio();

        fixture.Register(() => specimen);
}

}
`` `

PS .: O objetivo principal é reduzir a quantidade de código em meus testes, então devo evitar chamar With() para customizar os valores para cada teste. Existe uma maneira adequada de fazer isso?

Atualização: Acabei de ler esta resposta de @ploeh : https://github.com/AutoFixture/AutoFixture/issues/361#issuecomment -86873060 que pode ser o que estou procurando.
Essa abordagem tem muitas desvantagens: primeiro, parece realmente contra-intuitivo chamar Create<List<Foo>>() porque meio que contraria o propósito do que esperar de CreateMany<Foo> ; que geraria uma lista de tamanho codificado> (?). Outra desvantagem é que eu teria que ter duas personalizações para cada entidade; um para criar coleções personalizadas e outro para criar uma única instância, já que estamos substituindo o comportamento de Create<T> para criar coleções.

question

Todos 11 comentários

Este parece ser um bom candidato para uma pergunta do Stack Overflow . Não há nada de errado em fazer uma pergunta aqui, mas acho que essa pergunta é do tipo em que uma resposta pode beneficiar outras pessoas também, e é mais fácil encontrar perguntas e respostas no Stack Overflow em comparação com os problemas do GitHub. Por um lado, o Google faz um bom trabalho de indexação de questões do Stack Overflow, enquanto parece que classifica os problemas do GitHub (fechados) abaixo ...

@ploeh eu concordo com você ....
Acabei de criar uma pergunta do SO com base neste problema. Este é o link para referências futuras 👍

@ploeh Obrigado por responder à pergunta no SO: blush:

@thiagomajesk Na verdade, não há problemas em responder às perguntas aqui. O motivo pelo qual Mark sugeriu o SO são os recursos de pesquisa que ele oferece. No momento, Mark também está monitorando o SO, então se você postar uma pergunta lá, há mais chances de obter sua experiência (como você conseguiu desta vez): sweat_smile:

@zvirja Obrigado por esclarecer isso 😄

@zvirja Vou encerrar a pergunta sobre o SO com base na ótima resposta do @ploeh .
Mas eu gostaria de pedir um pouco mais de informações sobre os detalhes de implementação deste.
Como não quero me afastar ainda mais do assunto, acho que seria melhor perguntar sobre esses detalhes aqui. Então, eu tenho esta implementação:

`` `c #
public class UniqueShortGuidBuilder: ISpecimenBuilder
{
private readonly string propName;
comprimento int lenght somente leitura privado;

public UniqueShortGuidBuilder(Expression<Func<TEntity, string>> expr, int lenght)
{
    propName = ((MemberExpression)expr.Body).Member.Name;
    this.lenght = lenght;
}

criar objeto público (solicitação de objeto, contexto ISpecimenContext)
{
var pi = solicitação como PropertyInfo;

if (pi == null || pi.PropertyType != typeof(string) || pi.Name != propName)
    return new NoSpecimen();

return Guid.NewGuid().ToString().Substring(0, lenght);

}
`` `

Eu esperava que, ao criar muitos objetos com AutoFixture, o ISpecimenBuilder.Create fosse chamado várias vezes, gerando Guids distintos para minhas entidades; mas aparentemente isso não é verdade e eles estão compartilhando o mesmo.

Bem, acabei de testar seu código e funcionou exatamente como desejado:

`` `c #
classe pública Foo
{
public string PropertyToCustomize {get; definir; }
}

public class UniqueShortGuidBuilder: ISpecimenBuilder
{
private readonly string propName;
comprimento int lenght somente leitura privado;

public UniqueShortGuidBuilder(Expression<Func<TEntity, string>> expr, int lenght)
{
    propName = ((MemberExpression) expr.Body).Member.Name;
    this.lenght = lenght;
}

public object Create(object request, ISpecimenContext context)
{
    var pi = request as PropertyInfo;

    if (pi == null || pi.PropertyType != typeof(string) || pi.Name != propName)
        return new NoSpecimen();

    return Guid.NewGuid().ToString().Substring(0, lenght);
}

}

[Facto]
public void TestCustomization ()
{
var fixture = new Fixture ();
fixture.Customizations.Add (novo UniqueShortGuidBuilder(x => x.PropertyToCustomize, 5));

var manyFoos = fixture.CreateMany<Foo>(10);

var uniqueIds = manyFoos
    .Select(x => x.PropertyToCustomize)
    .Where(x => x.Length == 5)
    .Distinct()
    .ToArray();
Assert.Equal(10, uniqueIds.Length);

}
`` `

Portanto, gostaria de pedir novamente para você ver como o seu código real difere, para que possamos entender por que essa diferença é importante 😟

@zvirja Isso é estranho. O código abaixo é uma reprodução mínima do problema que estou tendo.
Se eu remover Fixture.Customize(new FooCustomization()); o teste será aprovado, caso contrário, não.

`` `c #
using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
usando AutoFixture.Kernel;
using System.Linq.Expressions;
using System.Reflection;
usando AutoFixture;
usando System.Linq;

namespace UnitTestProject1
{
classe pública Foo
{
public string PropertyToCustomize {get; definir; }
}

public class FooCustomization : ICustomization
{
    public void Customize(IFixture fixture)
    {
        var specimen = fixture.Build<Foo>()
            .OmitAutoProperties()
            .With(x => x.PropertyToCustomize)
            .Create();

        fixture.Register(() => specimen);
    }
}

public class UniqueShortGuidBuilder<TEntity> : ISpecimenBuilder
{
    private readonly string propName;
    private readonly int lenght;

    public UniqueShortGuidBuilder(Expression<Func<TEntity, string>> expr, int lenght)
    {
        propName = ((MemberExpression)expr.Body).Member.Name;
        this.lenght = lenght;
    }

    public object Create(object request, ISpecimenContext context)
    {
        var pi = request as PropertyInfo;

        if (pi == null || pi.PropertyType != typeof(string) || pi.Name != propName)
            return new NoSpecimen();

        return Guid.NewGuid().ToString().Substring(0, lenght);
    }
}

[TestClass]
public class UnitTest1
{
    public static Fixture Fixture { get; private set; }

    [ClassInitialize]
    public static void ClassInitialize(TestContext context)
    {
        Fixture = new Fixture();
        Fixture.Customizations.Add(new UniqueShortGuidBuilder<Foo>(x => x.PropertyToCustomize, 5));
        Fixture.Customize(new FooCustomization());
    }

    [TestMethod]
    public void TestMethod1()
    {
        var manyFoos = Fixture.CreateMany<Foo>(10);

        var uniqueIds = manyFoos
            .Select(x => x.PropertyToCustomize)
            .Where(x => x.Length == 5)
            .Distinct()
            .ToArray();

        Assert.AreEqual(10, uniqueIds.Length);
    }
}

}
`` `

Recomendação: se você depende de retirar os primeiros _n_ caracteres de uma string GUID, não nomeie a classe UniqueShortGuidBuilder . Os valores podem ser bastante únicos quando o comprimento é 5, mas se você definir como 1, provavelmente verá colisões. Em qualquer caso, a exclusividade não é garantida.

Se você só se preocupa com o comprimento da string, mais a exclusividade não garantida, por que não simplesmente pedir à AutoFixture um número entre, digamos, 0 e 99.999, e transformá-lo em uma string? IIRC, os números são únicos até que o intervalo se esgote.

Se eu remover o Fixture.Customize (new FooCustomization ()); o teste vai passar, caso contrário, não.

Bem, na verdade esse é o comportamento esperado se você analisar a situação com mais cuidado 😅 Por favor, veja minha resposta em # 962. A implementação fornecida de FooCustomization cria uma instância do tipo Foo e configura o fixture para sempre retornar essa instância. Portanto, quando mais tarde você criar várias instâncias do tipo Foo , sempre que o mesmo objeto for retornado e seu construtor não for mais chamado.

Se você quiser que uma nova instância seja criada para cada vez que você solicitar Foo , use a API Customize<> como sugeri aqui .

@ploeh Obrigado pela dica, mas esse código é apenas um exemplo grosseiro para fins de teste. Para casos reais, o comprimento não será ridiculamente curto para afetar a singularidade do Guids 😄.

Se você só se preocupa com o comprimento da string, mais a exclusividade não garantida, por que não simplesmente pedir à AutoFixture um número entre, digamos, 0 e 99.999, e transformá-lo em uma string?

Aposto que é uma boa solução para a maioria dos casos, mas não posso fazer isso neste caso específico por causa das validações alfanuméricas em meu sistema. Além disso, tenho validações mais complexas envolvendo esse campo.

Bem, na verdade esse é o comportamento esperado se você analisar a situação com mais cuidado

@zvirja Fica claro após sua resposta em # 962. Mas para mim esse comportamento não era óbvio, uma vez que não há documentação inicial (explícita) sobre a AutoFixtura (como indiquei aqui ) 😁.

Para finalizar, obrigado pela paciência e pelos excelentes esclarecimentos.
Tenho certeza de que isso será útil para outras pessoas no futuro também 😉.

Ok, é justo, posso ter uma tendência a levar as coisas muito ao pé da letra 😄

Esta página foi útil?
0 / 5 - 0 avaliações

Questões relacionadas

TroyHouston picture TroyHouston  ·  6Comentários

JoshKeegan picture JoshKeegan  ·  6Comentários

joelleortiz picture joelleortiz  ·  4Comentários

josh-degraw picture josh-degraw  ·  4Comentários

ploeh picture ploeh  ·  7Comentários