Autofixture: A aplicação de personalizações da classe base ou interface falha em 4.0.0-rc1 (regressão)

Criado em 20 nov. 2017  ·  10Comentários  ·  Fonte: AutoFixture/AutoFixture

Os seguintes testes são bem-sucedidos na AutoFixação 3.51.0 mas falham em 4.0.0-rc1 :
(mesmo no último commit no master: c856cd6 )

`` `c #
[Facto]
public void ShouldApplyCustomizationsFromBaseClass ()
{
var fixture = new Fixture ();
fixture.Customize(c => c.Sem (bc => bc.Id));
var res = fixture.Create();
Assert.Equal (0, res.Id);
}

[Facto]
public void ShouldApplyCustomizationsFromInterface ()
{
var fixture = new Fixture ();
fixture.Customize(c => c.Sem (bc => bc.Id));
var res = fixture.Create();
Assert.Equal (0, res.Id);
}

interface pública IInterface
{
int Id {get; definir; }
}

public class BaseClass
{
public int Id {get; definir; }
}

public class ImplClass: BaseClass, IInterface
{

}
`` `

question

Todos 10 comentários

Obrigado por levantar a questão. Bem, esta é a mudança desejada e foi aplicada de propósito.

Anteriormente, tivemos um problema quando a personalização de um subtipo poderia afetar outro subtipo. Por exemplo:

`` `c #
class Base {public string Common {get; definir; }}
classe Child1: Base {}
classe Child2: Base {}

[Facto]
public void VerifyCustomizationAreNotAffected ()
{
var fixture = new Fixture ();
fixture.Customize(c => c. Com (x => x.Common, "dummy"));

var result = fixture.Create<Child2>();

Assert.NotNull(result.Common);

}


In the v3 this test failed, causing a lot of confusion to people. As you might imagine, the scenarios were more complicated and it was very non-obvious why the particular members are not initialized. 

For instance, the following code will not work, which again proves that API is not designed for that.
```c#
fixture.Customize<Base>(c => c.With(x => x.Common, "foo"));

Portanto, para corrigir esse problema , mudamos a abordagem , de forma que agora a personalização de um tipo não pode afetar outros tipos, mesmo que pertençam à mesma linha de herança. Como você pode ver naquele PR, ele corrigiu vários problemas de usabilidade e adicionou mais clareza (embora a mudança esteja realmente quebrando).

Se ainda quiser omitir as propriedades de base / interface, você pode usar o seguinte snippet:
`` `c #
classe privada SamePropertySpecification: IRequestSpecification
{
private readonly Type _declaringType;
private readonly string _name;

public SamePropertySpecification(Type declaringType, string name)
{
    _declaringType = declaringType;
    _name = name;
}

public bool IsSatisfiedBy(object request)
{
    if (request is PropertyInfo pi)
    {
        return pi.DeclaringType == this._declaringType &&
               pi.Name.Equals(this._name, StringComparison.Ordinal);
    }

    return false;
}

}

[Facto]
public void TestBasePropertyOmitting ()
{
var fixture = new Fixture ();

fixture.Customizations.Add(new Omitter(new SamePropertySpecification(typeof(Base), nameof(Base.Common))));

var result = fixture.Create<Child1>();
Assert.Null(result.Common);

}
`` `

Se você precisa desse recurso com frequência, pode criar seu próprio método de extensão para fixture . No entanto, eu votaria para não ter esse recurso pronto para uso, pois parece muito confuso.

Ok, é bom saber que foi intencional. Eu definitivamente concordo que foi confuso, mas se puder ser aplicado explicitamente, é um bom recurso. Tentará seu snippet (e provavelmente fará o método de extensão).

A propósito: isso é algo que você deseja adicionar à lista de alterações importantes? Pode evitar que a mesma pergunta seja feita.

Não há como optar pelo comportamento antigo para qualquer uma das personalizações de todas as personalizações, certo?

Isso é algo que você deseja adicionar à lista de alterações importantes?

Bem, isso é mencionado na seção de correções de bugs . Na verdade, esta não é uma alteração significativa, pois o comportamento anterior nunca foi declarado como um "recurso". Em vez disso, esse foi um efeito colateral indesejado e foi finalmente eliminado 😉

Não há como optar pelo comportamento antigo para qualquer uma das personalizações de todas as personalizações, certo?

Não, acredito que não existe essa maneira, pois retrabalhei totalmente a abordagem.

Teste o snippet e, se funcionar, fique à vontade para encerrar o problema. Ou entre em contato se tiver mais perguntas 😃

Eu ajustei para que também funcione para interfaces e fiz um método de extensão para que possa ser usado em 'Fixture.Customize(...) '. Parece um pouco mais hacky do que eu gostaria, especialmente porque a maneira como funcionou na v3 foi perfeita para nós. Mas, como nosso único caso de uso é omitir propriedades e (mais importante 😉) funcionar, isso resolverá o problema. Obrigado!

@nphmuller Desculpe ter quebrado seu código limpo e organizado e agora você precisa sobreviver 😄 Mas eu acredito que seu sacrifício foi feito para o bem de todos nós, então você não vai nos culpar muito 😅

Sem culpa alguma. Compreenda perfeitamente o seu raciocínio e até concorde. É uma merda para mim 😉

Pode ser que mergulhe no problema mais tarde para ver se há uma maneira mais limpa. Por enquanto está bem.

Só por curiosidade - qual é o seu caso de uso real para isso? Porque nunca atendi a uma necessidade desse tipo de funcionalidade. Normalmente, quando eu escrevo um teste, preciso personalizar apenas um tipo específico, em vez de configurar o tipo base ou de interface. Você está escrevendo algum tipo de customização global?

Neste caso, usamos AutoFixture como um gerador de dados de teste para testes de integração do Entity Framework.

Temos alguns tipos de base ou interfaces que nossas entidades usam que contêm propriedades comuns. Id, tenant-id (e propriedade de navegação), coisas assim.

Por exemplo, uma dessas propriedades é qual usuário criou o objeto. Esta propriedade não é muito interessante para a maioria dos testes e, se fosse gerada, criaria um grande gráfico (a classe User também possui muitas propriedades de navegação) que teria que ser gerado e inserido no banco de dados. Isso tornaria o teste lento ou até mesmo falharia no nível de banco de dados porque o objeto não foi feito para ser inserido dessa forma.

Portanto, é mais fácil simplesmente excluir essas propriedades e ativá-las quando necessário. No nível da classe base, porque senão a mesma regra de omissão teria que ser escrita para cada tipo de entidade.

@nphmuller Obrigado pelo esclarecimento, agora está claro 😉 Sim, parece que o cenário faz sentido, mas devo admitir que parece algo de que você raramente precisa.

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

Questões relacionadas

DeafLight picture DeafLight  ·  5Comentários

Ephasme picture Ephasme  ·  3Comentários

ploeh picture ploeh  ·  3Comentários

ploeh picture ploeh  ·  7Comentários

zvirja picture zvirja  ·  4Comentários