web-dev-qa-db-fra.com

Test unitaire pour tester la création d'un objet de domaine

J'ai un test unitaire, qui ressemble à ceci:

[Test]
public void Should_create_person()
{
     Assert.DoesNotThrow(() => new Person(Guid.NewGuid(), new DateTime(1972, 01, 01));
}

J'affirme qu'un objet Personne est créé ici, c'est-à-dire que la validation n'échoue pas. Par exemple, si le Guid est nul ou si la date de naissance est antérieure au 01/01/1900, la validation échouera et une exception sera levée (ce qui signifie que le test échoue).

Le constructeur ressemble à ceci:

public Person(Id id, DateTime dateOfBirth) :
        base(id)
    {
        if (dateOfBirth == null)
            throw new ArgumentNullException("Date of Birth");
        elseif (dateOfBith < new DateTime(1900,01,01)
            throw new ArgumentException("Date of Birth");
        DateOfBirth = dateOfBirth;
    }

Est-ce une bonne idée pour un test?

Remarque : je suis une approche classique des tests unitaires du modèle de domaine si cela détient n'importe quel roulement.

11
w0051977

C'est un test valide (bien que trop zélé) et je le fais parfois pour tester la logique du constructeur, cependant, comme Laiv l'a mentionné dans les commentaires, vous devriez vous demander pourquoi.

Si votre constructeur ressemble à ceci:

public Person(Guid guid, DateTime dob)
{
  this.Guid = guid;
  this.Dob = dob;
}

Y a-t-il beaucoup d'intérêt à tester s'il lance? Que les paramètres soient correctement attribués, je peux comprendre, mais votre test est plutôt exagéré.

Cependant, si votre test fait quelque chose comme ceci:

public Person(Guid guid, DateTime dob)
{
  if(guid == default(Guid)) throw new ArgumentException("Guid is invalid");
  if(dob == default(DateTime)) throw new ArgumentException("Dob is invalid");

  this.Guid = guid;
  this.Dob = dob;
}

Ensuite, votre test devient plus pertinent (car vous lancez des exceptions quelque part dans le code).

Une chose que je dirais, c'est généralement une mauvaise pratique d'avoir beaucoup de logique dans votre constructeur. La validation de base (comme les vérifications nulles/par défaut que je fais ci-dessus) est correcte. Mais si vous vous connectez à des bases de données et chargez les données de quelqu'un, c'est là que le code commence à vraiment sentir ...

Pour cette raison, si votre constructeur mérite d'être testé (car il y a beaucoup de logique), peut-être que quelque chose d'autre ne va pas.

Vous allez presque certainement avoir d'autres tests couvrant cette classe dans les couches de logique métier, les constructeurs et les affectations de variables vont presque certainement obtenir une couverture complète de ces tests. Par conséquent, il peut être inutile d'ajouter des tests spécifiques spécifiquement pour le constructeur. Cependant, rien n'est noir et blanc et je n'aurais rien contre ces tests si je les examinais - mais je me demanderais s'ils ajoutent beaucoup de valeur au-delà des tests ailleurs dans votre solution.

Dans votre exemple:

public Person(Id id, DateTime dateOfBirth) :
        base(id)
    {
        if (dateOfBirth == null)
            throw new ArgumentNullException("Date of Birth");
        elseif (dateOfBith < new DateTime(1900,01,01)
            throw new ArgumentException("Date of Birth");
        DateOfBirth = dateOfBirth;
    }

Vous faites non seulement la validation, mais vous appelez également un constructeur de base. Pour moi, cela fournit plus de raisons d'avoir ces tests car la logique constructeur/validation est maintenant divisée en deux classes, ce qui diminue la visibilité et augmente le risque de changement inattendu.

TLDR

Ces tests ont une certaine valeur, mais la logique de validation/affectation est susceptible d'être couverte par d'autres tests dans votre solution. S'il y a beaucoup de logique dans ces constructeurs qui nécessite des tests importants, cela me suggère qu'il y a une mauvaise odeur de code qui se cache là-dedans.

18
Liath

Déjà une bonne réponse ici, mais je pense qu'une chose supplémentaire mérite d'être mentionnée.

En faisant TDD "par le livre", il faut d'abord écrire un test qui appelle le constructeur, avant même que le constructeur soit implémenté. Ce test pourrait en fait ressembler à celui que vous avez présenté, même s'il n'y aurait aucune logique de validation à l'intérieur de l'implémentation du constructeur.

Notez également que pour TDD, il faut d'abord écrire un autre test comme

  Assert.Throws<ArgumentException>(() => new Person(Guid.NewGuid(), 
        new DateTime(1572, 01, 01));

avant d'ajouter la vérification de DateTime(1900,01,01) au constructeur.

Dans le contexte TDD, le test illustré est parfaitement logique.

12
Doc Brown