Aqui está uma proposta de comportamento do limitador de profundidade de geração.
fixture.Behaviors.Add (new GenerationDepthBehavior (2));
Tive que reimplementar ComposeIfMultiple para teste porque é interno.
`` `c #
public class GenerationDepthBehavior: ISpecimenBuilderTransformation
{
private const int DefaultGenerationDepth = 1;
private readonly int generationDepth;
public GenerationDepthBehavior() : this(DefaultGenerationDepth)
{
}
public GenerationDepthBehavior(int generationDepth)
{
if (generationDepth < 1)
throw new ArgumentOutOfRangeException(nameof(generationDepth), "Generation depth must be greater than 0.");
this.generationDepth = generationDepth;
}
public ISpecimenBuilderNode Transform(ISpecimenBuilder builder)
{
if (builder == null) throw new ArgumentNullException(nameof(builder));
return new GenerationDepthGuard(builder, new GenerationDepthHandler(), this.generationDepth);
}
}
interface pública IGenerationDepthHandler
{
object HandleGenerationDepthLimitRequest(object request, IEnumerable<object> recordedRequests, int depth);
}
public class DepthSeededRequest: SeededRequest
{
public int Depth {get; }
public DepthSeededRequest (solicitação de objeto, semente de objeto, profundidade interna): base (solicitação, semente)
{
Profundidade = profundidade;
}
}
public class GenerationDepthGuard: ISpecimenBuilderNode
{
ThreadLocal somente leitura privado
= novo ThreadLocal
private Stack<DepthSeededRequest> GetMonitoredRequestsForCurrentThread() => this.requestsByThread.Value;
public GenerationDepthGuard(ISpecimenBuilder builder)
: this(builder, EqualityComparer<object>.Default)
{
}
public GenerationDepthGuard(
ISpecimenBuilder builder,
IGenerationDepthHandler depthHandler)
: this(
builder,
depthHandler,
EqualityComparer<object>.Default,
1)
{
}
public GenerationDepthGuard(
ISpecimenBuilder builder,
IGenerationDepthHandler depthHandler,
int generationDepth)
: this(
builder,
depthHandler,
EqualityComparer<object>.Default,
generationDepth)
{
}
public GenerationDepthGuard(ISpecimenBuilder builder, IEqualityComparer comparer)
{
this.Builder = builder ?? throw new ArgumentNullException(nameof(builder));
this.Comparer = comparer ?? throw new ArgumentNullException(nameof(comparer));
this.GenerationDepth = 1;
}
public GenerationDepthGuard(
ISpecimenBuilder builder,
IGenerationDepthHandler depthHandler,
IEqualityComparer comparer)
: this(
builder,
depthHandler,
comparer,
1)
{
}
public GenerationDepthGuard(
ISpecimenBuilder builder,
IGenerationDepthHandler depthHandler,
IEqualityComparer comparer,
int generationDepth)
{
if (builder == null) throw new ArgumentNullException(nameof(builder));
if (depthHandler == null) throw new ArgumentNullException(nameof(depthHandler));
if (comparer == null) throw new ArgumentNullException(nameof(comparer));
if (generationDepth < 1)
throw new ArgumentOutOfRangeException(nameof(generationDepth), "Generation depth must be greater than 0.");
this.Builder = builder;
this.GenerationDepthHandler = depthHandler;
this.Comparer = comparer;
this.GenerationDepth = generationDepth;
}
public ISpecimenBuilder Builder { get; }
public IGenerationDepthHandler GenerationDepthHandler { get; }
public int GenerationDepth { get; }
public int CurrentDepth { get; }
public IEqualityComparer Comparer { get; }
protected IEnumerable RecordedRequests => this.GetMonitoredRequestsForCurrentThread();
public virtual object HandleGenerationDepthLimitRequest(object request, int currentDepth)
{
return this.GenerationDepthHandler.HandleGenerationDepthLimitRequest(
request,
this.GetMonitoredRequestsForCurrentThread(), currentDepth);
}
public object Create(object request, ISpecimenContext context)
{
if (request is SeededRequest)
{
int currentDepth = -1;
var requestsForCurrentThread = this.GetMonitoredRequestsForCurrentThread();
if (requestsForCurrentThread.Count > 0)
{
currentDepth = requestsForCurrentThread.Max(x => x.Depth) + 1;
}
DepthSeededRequest depthRequest = new DepthSeededRequest(((SeededRequest)request).Request, ((SeededRequest)request).Seed, currentDepth);
if (depthRequest.Depth >= this.GenerationDepth)
{
return HandleGenerationDepthLimitRequest(request, depthRequest.Depth);
}
requestsForCurrentThread.Push(depthRequest);
try
{
return this.Builder.Create(request, context);
}
finally
{
requestsForCurrentThread.Pop();
}
}
else
{
return this.Builder.Create(request, context);
}
}
public virtual ISpecimenBuilderNode Compose(
IEnumerable<ISpecimenBuilder> builders)
{
var composedBuilder = ComposeIfMultiple(
builders);
return new GenerationDepthGuard(
composedBuilder,
this.GenerationDepthHandler,
this.Comparer,
this.GenerationDepth);
}
internal static ISpecimenBuilder ComposeIfMultiple(IEnumerable<ISpecimenBuilder> builders)
{
ISpecimenBuilder singleItem = null;
List<ISpecimenBuilder> multipleItems = null;
bool hasItems = false;
using (var enumerator = builders.GetEnumerator())
{
if (enumerator.MoveNext())
{
singleItem = enumerator.Current;
hasItems = true;
while (enumerator.MoveNext())
{
if (multipleItems == null)
{
multipleItems = new List<ISpecimenBuilder> { singleItem };
}
multipleItems.Add(enumerator.Current);
}
}
}
if (!hasItems)
{
return new CompositeSpecimenBuilder();
}
if (multipleItems == null)
{
return singleItem;
}
return new CompositeSpecimenBuilder(multipleItems);
}
public virtual IEnumerator<ISpecimenBuilder> GetEnumerator()
{
yield return this.Builder;
}
IEnumerator IEnumerable.GetEnumerator()
{
return this.GetEnumerator();
}
}
public class GenerationDepthHandler: IGenerationDepthHandler
{
public object HandleGenerationDepthLimitRequest (
pedido de objeto,
IEnumerable
Olá, obrigado por compartilhar a ideia! : +1:
Você poderia descrever o raciocínio por trás desse recurso? Você tem algum cenário específico em que o considere útil?
Oi, eu o uso principalmente para entidades ORM ou classes API externas onde a profundidade pode se propagar indefinidamente, mas não é recursiva.
Eu uso o AutoFixture para testes de unidade, testes de integração e testes funcionais. Teste de unidade para as principais funcionalidades do framework corporativo. Testes de integração com testes para unidades complexas de trabalho com padrão de repositório e acesso a um banco de dados de teste. Teste funcional com acesso a um aplicativo completo.
Coloquei diretamente em AutoMoqDataAttribute
`` `c #
public class AutoMoqDataAttribute: AutoDataAttribute
{
///
/// Construtor padrão.
///
/// O padrão é 0 para profundidade de geração ilimitada.
public AutoMoqDataAttribute (int generationDepth = 0)
: base (() =>
{
IFixture fixture = new Fixture (). Customize (new AutoMoqCustomization ());
fixture.Behaviors.OfType<ThrowingRecursionBehavior>().ToList()
.ForEach(b => fixture.Behaviors.Remove(b));
fixture.Behaviors.Add(new OmitOnRecursionBehavior());
if (generationDepth > 0)
{
fixture.Behaviors.Add(new GenerationDepthBehavior(generationDepth));
}
return fixture;
})
{
}
}
`` `
Eu também sugeriria tornar o ComposeIfMultiple público. Isso tornaria a implementação do comportamento personalizado mais fácil.
Eu entendo o que o comportamento faz, a questão principal é o propósito e os cenários.
Oi, eu o uso principalmente para entidades ORM ou classes API externas onde a profundidade pode se propagar indefinidamente, mas não é recursiva.
Então, pelo que eu entendi, o principal objetivo que você está almejando é o desempenho. Do ponto de vista funcional, tudo funciona bem e você simplesmente gostaria de economizar alguns tiques da CPU.
É um pouco difícil julgar se gostaríamos de enviar esse recurso pronto para uso ou não. Eu entendo que você pode realmente ter um modelo de tipo com aninhamento muito profundo e aí o ganho de desempenho é significativo. Mas também existem algumas desvantagens nisso:
É absolutamente normal que em sua equipe você exija esse recurso e concorde em usá-lo. No entanto, eu diria para dar a ele um uso comum, pois complica muito as coisas.
Existem muitos cenários potenciais e o AutoFixture tenta enviar apenas os recursos mais comuns. Os bits mais avançados são cobertos pelos recursos de extensibilidade. Neste caso específico, eu diria que esse recurso não é tão comum para torná-lo parte do produto. Outro ponto é que a implementação é muito pequena (veja abaixo), então não deve ser um problema implementá-la em seu projeto de teste localmente.
Eu também sugeriria tornar o ComposeIfMultiple público. Isso tornaria a implementação do comportamento personalizado mais fácil.
Já foi discutido # 657.
A propósito, a implementação pode ser um pouco simplificada (se não estiver faltando alguma coisa):
`` `c #
public class GenerationDepthBehavior: ISpecimenBuilderTransformation
{
public int Depth {get; }
public GenerationDepthBehavior(int depth)
{
Depth = depth;
}
public ISpecimenBuilderNode Transform(ISpecimenBuilder builder)
{
return new RecursionGuard(builder, new OmitOnRecursionHandler(), new IsSeededRequestComparer(), Depth);
}
private class IsSeededRequestComparer : IEqualityComparer
{
bool IEqualityComparer.Equals(object x, object y)
{
return x is SeededRequest && y is SeededRequest;
}
int IEqualityComparer.GetHashCode(object obj)
{
return obj is SeededRequest ? 0 : EqualityComparer<object>.Default.GetHashCode(obj);
}
}
}
`` `
@moodmosaic Você também poderia dar sua opinião? 😉
O exemplo mostrado em https://github.com/AutoFixture/AutoFixture/issues/1032#issuecomment -385724150 é a maneira preferida de usar esse comportamento ad-hoc . Neste ponto, não acho que devemos mudar o algoritmo embutido.
O que podemos considerar, no entanto, é
e ,
@moodmosaic Parece que a sugestão não é mudar o comportamento padrão, mas tornar GenerationDepthBehavior
uma parte da biblioteca AutoFixture (para que você possa habilitá-la sob demanda). Então, qual a sua opinião sobre isso? :piscadela:
@zvirja Na verdade, não é por motivos de desempenho. O problema com a propagação indefinida é o teste com falha com exceção de falta de memória.
@zvirja , @ malylemire1 , a menos que esse comportamento ad-hoc trate de uma ampla gama de cenários, não acho que precise ser integrado.
Não consigo identificar um MCVE , mas AFAICT, os problemas vêm de testes automatizados que não são realmente testes de unidade (por exemplo, testes de limite / integração).
AutoFixture não foi projetada principalmente para lidar com testes de integração, então alguns comportamentos ad-hoc (como o mostrado aqui) são garantidos: +1: eu não acho que tenha que fazer parte da biblioteca principal, no entanto.
@ malylemire1 Você ficaria bem se
Lamento não podermos incluir todos os recursos desejados no núcleo 😟 É um pouco difícil manter o equilíbrio entre o recurso fornecido e a quantidade de código que mantemos aqui. Parece que não será um grande problema para você implementar esse recurso ad-hoc em sua solução, portanto, você não será bloqueado.
Obrigado novamente pela sua proposta!
Fechar este problema como parte da limpeza. Obrigado novamente por compartilhar a ideia! 😉
@ malylemire1 Compreendo 100% o caso de uso.
Isso é algo que você pode disponibilizar em um pacote nuget?
Comentários muito úteis
@ malylemire1 Você ficaria bem se
Lamento não podermos incluir todos os recursos desejados no núcleo 😟 É um pouco difícil manter o equilíbrio entre o recurso fornecido e a quantidade de código que mantemos aqui. Parece que não será um grande problema para você implementar esse recurso ad-hoc em sua solução, portanto, você não será bloqueado.
Obrigado novamente pela sua proposta!