Un test unitaire est-il considéré comme fragile s'il échoue lorsque la logique métier change?

27

Veuillez consulter le code ci-dessous; il vérifie si une personne de sexe féminin est admissible à l'offre1:

[Fact]
public void ReturnsFalseWhenGivenAPersonWithAGenderOfFemale()
{
    var personId = Guid.NewGuid();
    var gender = "F";
    var person = new Person(personId, gender);

    var id = Guid.NewGuid();
    var offer1 = new Offer1(id,"Offer1");
    Assert.False(offer1.IsEligible(person));
}

Ce test unitaire réussit. Cependant, il échouera si «Offer1» est proposé aux femmes à l'avenir.

Est-il acceptable de dire - si la logique métier entourant l'offre 1 change, le test unitaire doit changer. Veuillez noter que dans certains cas (pour certaines offres) la logique métier est modifiée dans la base de données comme ceci:

update Offers set Gender='M' where offer=1;

et dans certains cas dans le modèle de domaine comme celui-ci:

if (Gender=Gender.Male)
{
  //do something
}

Veuillez également noter que dans certains cas, la logique de domaine derrière propose des changements régulièrement et dans certains cas, ce n'est pas le cas.

w0051977
la source
2
Pensez sous un autre angle: voulez-vous avoir des tests qui n'ont pas échoué lorsque vous changez de logique dans le système sous le test?
Fabio

Réponses:

77

Ce n'est pas fragile au sens habituel. Un test unitaire est considéré comme fragile s'il se casse en raison de changements d'implémentation qui n'affectent pas le comportement testé. Mais si la logique métier elle-même change, alors un test de cette logique est censé casser.

Cela dit, si la logique métier change en effet souvent, il n'est peut-être pas approprié de coder en dur les attentes dans les tests unitaires. Au lieu de cela, vous pouvez tester si les configurations de la base de données affectent les offres comme prévu.

Le nom du test Returns False When Given A Person With A Gender Of Femalene décrit pas une règle métier. Une règle commerciale serait quelque chose comme ça Offers Applicable to M should not be applied to persons of gender F.

Vous pouvez donc passer un test qui confirme que si une offre est définie comme applicable uniquement aux personnes de type M, une personne de type F ne sera pas indiquée comme éligible. Ce test garantira que la logique fonctionne même si la configuration des offres spécifiques change.

JacquesB
la source
@JaquesB, alors ce ne serait pas un test unitaire? ou serait-ce? Je pense que ce serait un test d'intégration si la base de données était impliquée. Est-ce correct? Dites-vous de ne pas utiliser de tests unitaires si la logique métier change beaucoup?
w0051977
@ w0051977: dépend de la façon dont vous écrivez le test. Si le test inclut réellement la modification d'un élément dans une base de données, ce serait un test d'intégration.
JacquesB
3
@ w0051977 meilleure idée - le référentiel ne doit pas être une dépendance du composant responsable de l'implémentation des règles métier. Avoir une orchestration de niveau supérieur qui appelle le référentiel et invoque ensuite les règles métier. Vous pouvez désormais tester séparément les règles métier.
Ant P
5
@ w0051977 bien sûr que c'est - les tests spécifient le comportement. Si les règles régissant le comportement d'un composant changent, les tests doivent changer pour refléter le changement de comportement. Ce qui ne devrait pas être modifié, ce sont des tests qui spécifient des comportements autres que ce qui change. Si le comportement est déterminé par la base de données, alors un test couvrant un autre code est intrinsèquement sans rapport et ne devrait pas avoir besoin de changer à moins que cette logique de base de données ne soit dans la portée du test. Cette portée est à définir et la sémantique de savoir s'il s'agit d'un test unitaire ou d'un test d'intégration n'est pas vraiment importante.
Ant P
3
@ w0051977 étendant quelque peu cette idée, si la logique métier change ou qu'un bogue est corrigé et que les tests n'ont pas besoin d'être ajustés, c'est un signe que les tests ne couvrent pas suffisamment de cas et devraient généralement être étendus.
Morgen
14

Lorsque la propriété est définie dans la base de données de production (ou un clone pour les tests), il ne s'agit pas d'un test unitaire . Un test unitaire vérifie une unité de travail et ne nécessite pas un état externe particulier pour fonctionner. Cela suppose que Offer1la base de données est définie comme une offre réservée aux hommes. C'est un état extérieur. Il s'agit donc davantage d'un test d'intégration , en particulier d'un système ou d'un test d' acceptation . Notez que les tests d'acceptation ne sont souvent pas scriptés (pas exécutés dans un cadre de test mais exécutés manuellement par des êtres humains).

Lorsque la propriété est définie dans le modèle de domaine avec une ifinstruction, le même test est un test unitaire. Et cela peut être fragile. Mais le vrai problème est que le code est fragile. En règle générale, votre code sera plus résistant si le comportement de l'entreprise est configurable plutôt que codé en dur. Parce qu'un déploiement précipité pour corriger une petite erreur de codage devrait être rare. Mais une exigence commerciale qui change sans préavis n'est qu'un mardi (quelque chose qui se produit chaque semaine).

Vous utilisez peut-être un framework de test unitaire pour exécuter le test. Mais les frameworks de tests unitaires ne se limitent pas à l'exécution de tests unitaires. Ils peuvent également exécuter des tests d'intégration.

Si vous écriviez un test unitaire, vous créeriez les deux personet à offer1partir de zéro sans vous fier à l'état de la base de données. Quelque chose comme

[Fact]
public void ReturnsFalseWhenGivenAPersonWithAGenderOfFemale()
{
    var personId = Guid.NewGuid();
    var gender = "F";
    var person = new Person(personId, gender);

    var id = Guid.NewGuid();
    var offer1 = new Offer1(id, "ReturnsFalseWhenGivenAPersonWithAGenderOfFemale");
    offer1.markLimitedToGender("M");

    Assert.False(offer1.IsEligible(person));
}

Notez que cela ne change pas en fonction de la logique métier. Ce n'est pas l'affirmation qui offer1rejette les femmes. C'est faire offer1le type d'offre qui rejette les femmes.

Vous pouvez créer et configurer la base de données dans le cadre du test. En C #, à l'aide de NUnit, ou dans JUnit de Java, vous devez configurer la base de données dans une Setupméthode. Vraisemblablement, votre framework de test a une notion similaire. Dans cette méthode, vous pouvez insérer des enregistrements dans la base de données avec SQL.

S'il vous est difficile d'écrire du code qui substitue une base de données de test à la base de données de production, cela ressemble à une faiblesse de test dans votre application. Pour les tests, il serait préférable d'utiliser quelque chose comme l'injection de dépendance qui permet la substitution. Ensuite, vous pouvez écrire des tests indépendants des règles métier actuelles.

Un avantage secondaire de cela est qu'il est souvent plus facile pour le propriétaire de l'entreprise (pas nécessairement le propriétaire de l'entreprise, plus comme la personne responsable de ce produit dans la hiérarchie de l'entreprise) de configurer directement les règles métier. Parce que si vous disposez de ce type de cadre technique, il est facile d'autoriser le propriétaire de l'entreprise à utiliser une interface utilisateur (UI) pour configurer l'offre. Le propriétaire de l'entreprise sélectionnerait la limitation dans l'interface utilisateur et émettrait l' markLimitedToGender("M")appel. Ensuite, lorsque l'offre est conservée dans la base de données, elle est stockée. Mais vous n'auriez pas besoin de stocker l'offre pour l'utiliser. Vos tests pourraient donc créer et configurer une offre qui n'existe pas dans la base de données.

Dans votre système tel que décrit, le propriétaire de l'entreprise devra soumettre une demande au groupe technique, qui émettra le code SQL approprié et mettra à jour les tests. Ou le groupe technique doit éditer votre code et vos tests (ou tests puis coder). Cela semble une approche plutôt lourde. Tu peux le faire. Mais votre logiciel (et pas seulement vos tests) serait moins fragile si vous n'aviez pas à le faire.

TL; DR : vous pouvez écrire des tests comme celui-ci, mais il vaut peut-être mieux écrire votre logiciel pour ne pas avoir à le faire.

mdfst13
la source
J'utilise la liberté d'améliorer certains détails mineurs dans votre réponse. Veuillez vérifier si j'ai bien compris vos intentions.
Doc Brown
Rien dans le message d'origine n'indique qu'une base de données est impliquée. Donc, prétendre qu'il suppose que Offer1 est déjà dans la base de données est bizarre.
Winston Ewert
2
@WinstonEwert: il y a une indication claire, vous devez lire la question plus attentivement. Je ne m'en étais pas rendu compte lors de la première lecture aussi, mais c'est bien de cela dont parle le PO.
Doc Brown
@ mdfst13, je suis désolé d'avoir raté ça. Cependant, ce que l'OP dit, c'est que les conditions sont parfois dans une base de données et parfois dans le modèle de domaine. Votre réponse est parfaitement correcte dans le premier cas et irrévérencieusement dans le second. Si vous modifiez votre réponse pour clarifier ce point, je supprimerai mon vote précipité.
Winston Ewert