Autofixture: Approche pour générer un échantillon aléatoire basé sur la personnalisation

Créé le 8 janv. 2018  ·  11Commentaires  ·  Source: AutoFixture/AutoFixture

Bonjour, je veux pouvoir générer des valeurs distinctes basées sur un ICustomization utilisant ISpecimenBuilder.CreateMany . Je me demandais quelle serait la meilleure solution puisque AutoFixture générera les mêmes valeurs pour toutes les entités.

```c#
classe publique FooCustomization : ICustomization
{
vide public Personnaliser (fixation Ifixture)
{
var spécimen = fixture.Build()
.OmitAutoProperties()
.With(x => x.CreationDate, DateTime.Now)
.With(x => x.Identifier, Guid.NewGuid().ToString().Substring(0, 6)) // Doit générer des valeurs distinctes
.Avec(x => x.Mail, $"[email protected]")
.Créer();

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

}
```

PS. : L'objectif principal est de réduire la quantité de code sur mes tests, je dois donc éviter d'appeler With() pour personnaliser les valeurs de chaque test. Y a-t-il une bonne façon de faire cela?

Mise à jour : je viens de lire cette réponse de @ploeh : https://github.com/AutoFixture/AutoFixture/issues/361#issuecomment -86873060 c'est peut-être ce que je recherche.
Cette approche a de nombreux inconvénients : Premièrement, il semble vraiment contre-intuitif d'appeler Create<List<Foo>>() car cela va à l'encontre de l'objectif de ce à quoi s'attendre de CreateMany<Foo> ; qui générerait une liste de taille codée en dur> (?). Un autre inconvénient est que je devrais avoir deux personnalisations pour chaque entité ; un pour créer des collections personnalisées et un autre pour créer une seule instance, puisque nous supplantons le comportement de Create<T> pour créer des collections.

question

Tous les 11 commentaires

Cela semble être un bon candidat pour une question Stack Overflow . Il n'y a rien de mal à poser une question ici, mais je pense que cette question est du genre où une réponse pourrait également profiter à d'autres personnes, et il est plus facile de trouver des questions et des réponses sur Stack Overflow par rapport aux problèmes GitHub. D'une part, Google fait un bon travail d'indexation des questions Stack Overflow, alors qu'il semble qu'il classe les problèmes GitHub (fermés) plus bas...

@ploeh je suis d'accord avec toi....
Je viens de créer une question SO basée sur ce problème. Ceci est le lien pour les futures références

@ploeh Merci d'avoir répondu à la question sur SO :blush:

@thiagomajesk En fait, il n'y a aucun problème à répondre aux questions ici. La raison pour laquelle Mark a suggéré SO est les capacités de recherche qu'il offre. Mark surveille également actuellement SO, donc si vous posez une question là-bas, il y a plus de chances d'obtenir son expertise (comme vous l'avez cette fois) :sweat_smile:

@zvirja Merci d'avoir précisé cela 😄

@zvirja Je vais clore la question sur SO en me basant sur l'excellente réponse de
Mais j'aimerais demander un peu plus d'informations sur les détails de mise en œuvre de celui-ci.
Comme je ne veux pas m'éloigner encore plus du sujet, je pense qu'il serait préférable de poser des questions sur ces détails ici. J'ai donc cette implémentation :

```c#
classe publique UniqueShortGuidBuilder: ISpecimenBuilder
{
chaîne privée en lecture seule propName;
longueur int privée en lecture seule ;

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

objet public Créer (demande d'objet, contexte ISpecimenContext)
{
var pi = demande en tant que PropertyInfo ;

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

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

}
```

Je m'attendais à ce que lors de la création de nombreux objets avec AutoFixture, le ISpecimenBuilder.Create soit appelé plusieurs fois, générant des Guids distincts pour mes entités ; mais apparemment ce n'est pas vrai et ils partagent en fait le même.

Eh bien, je viens de tester votre code et cela a fonctionné exactement comme vous le souhaitiez :

```c#
classe publique Foo
{
chaîne publique PropertyToCustomize { get; ensemble; }
}

classe publique UniqueShortGuidBuilder: ISpecimenBuilder
{
chaîne privée en lecture seule propName;
longueur int privée en lecture seule ;

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

}

[Fait]
Public void TestCustomization()
{
var fixture = new Fixture();
fixture.Customizations.Add (nouveau UniqueShortGuidBuilder(x => x.PropriétéÀPersonnaliser, 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);

}
```

Par conséquent, j'aimerais à nouveau vous demander de voir en quoi votre code réel diffère, afin que nous puissions comprendre pourquoi cette différence est importante 😟

@zvirja C'est bizarre. Le code ci-dessous est une reproduction minimale du problème que j'ai.
Si je supprime le Fixture.Customize(new FooCustomization()); le test passera, sinon non.

```c#
en utilisant le système ;
en utilisant Microsoft.VisualStudio.TestTools.UnitTesting ;
en utilisant AutoFixture.Kernel ;
en utilisant System.Linq.Expressions ;
en utilisant System.Reflection ;
en utilisant AutoFixture ;
en utilisant System.Linq;

espace de noms UnitTestProject1
{
classe publique Foo
{
chaîne publique PropertyToCustomize { get; ensemble; }
}

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

}
```

Recommandation : si vous comptez sur la suppression des _n_ premiers caractères d'une chaîne GUID, ne nommez pas la classe UniqueShortGuidBuilder . Les valeurs peuvent être assez uniques lorsque la longueur est de 5, mais si vous la définissez sur 1, vous verrez probablement des collisions. Dans tous les cas, l'unicité n'est pas garantie.

Si vous ne vous souciez que de la longueur de la chaîne, plus l'unicité non garantie, pourquoi ne pas simplement demander à AutoFixture un nombre compris entre, disons, 0 et 99 999, et le transformer en chaîne ? IIRC, les numéros sont uniques jusqu'à ce que la gamme soit épuisée.

Si je supprime le Fixture.Customize(new FooCustomization()); le test passera, sinon non.

Eh bien, en fait, c'est le comportement attendu si vous examinez la situation plus attentivement 😅 Veuillez voir ma réponse au n°962. L'implémentation fournie de FooCustomization crée une instance de type Foo et configure fixture pour toujours renvoyer cette instance. Par conséquent, lorsque vous créez ultérieurement plusieurs instances de type Foo , à chaque fois le même objet est renvoyé et votre générateur n'est plus appelé.

Si vous souhaitez qu'une nouvelle instance soit créée à chaque fois que vous demandez Foo , utilisez l'API Customize<> comme je l'ai suggéré ici .

@ploeh Merci pour le conseil, mais ce code n'est qu'un exemple brut à des fins de test. Pour les cas réels, la longueur ne sera pas ridiculement courte pour affecter l'unicité des Guids 😄.

Si vous ne vous souciez que de la longueur de la chaîne, plus l'unicité non garantie, pourquoi ne pas simplement demander à AutoFixture un nombre compris entre, disons, 0 et 99 999, et le transformer en chaîne ?

Je parie que c'est une bonne solution pour la plupart des cas, mais je ne peux pas le faire pour ce cas spécifique en raison des validations alphanumériques dans mon système. En plus de cela, j'ai des validations plus complexes impliquant ce domaine.

Eh bien, en fait, c'est le comportement attendu si vous examinez la situation plus attentivement

@zvirja C'est clair après votre réponse au #962. Mais pour moi, ce comportement n'était pas évident, car il n'y a pas de documentation initiale (explicite) sur AutoFixture (comme je l'ai souligné ici ) 😁.

Pour conclure, merci à vous deux pour la patience et toutes les excellentes explications.
Je suis sûr que cela sera utile à d'autres à l'avenir aussi 😉.

D'accord, d'accord, j'ai peut-être tendance à prendre les choses trop littéralement 😄

Cette page vous a été utile?
0 / 5 - 0 notes