web-dev-qa-db-fra.com

Les cas de test font tout le travail par la méthode d'assistance - mauvaise pratique?

Considérez une suite de tests comme celle-ci:

public class MyTestSuite {
   @Test
   public void test_something() {
      testHelperMethod("value11", "value12", "value13");
   }

   @Test
   public void test_something_else_meaningful_name() {
      testHelperMethod("othervalue21", "othervalue22", "othervalue23");
   }

   // ...

   private void testHelperMethod(String value1, String value2, String value3) {
       // set up part (independent of value1, value2, value3)

       // action part (dependent on values)

       // assertion part (dependent on values)

       // tear down part (independent of values)
   }
}

En d'autres termes, tous les cas de test sont exécutés via une seule méthode d'assistance paramétrée, structurée en fonction du schéma d'ARRANGER-ACT-ASSERT. Les cas de test appellent simplement cette méthode avec divers paramètres, en fonction de ce qui doit être testé exactement.

Question: Quel est l'inconvénient, le cas échéant, de structurer les tests comme celui-ci, et cela (anti?) - modèle a un nom commun?

remarques

  1. J'ai essayé de google pour cela pendant une longue période, y compris - SO et sur le - blog , mais je n'ai trouvé rien d'utile jusqu'à présent.

  2. Ceci question est le plus proche que j'ai trouvé, mais les réponses y adressent d'autres aspects/problèmes, à savoir:

    • assertions/Code de configuration Intermixed (pas le cas ici, car toutes les assertions sont à la fin de la méthode de l'assistance)
    • plus d'affirmations par test (également le cas dans mon exemple, mais je pense que c'est un problème non liée)
    • la responsabilité de la méthode de l'assistance n'est pas claire: je pense dans mon cas it est Clear, juste il est différent de la "traditionnelle"
  3. Pour être plus précis, la partie d'affirmation dans testHelperMethod est également une méthode utilitaire distincte (appelée avec de nombreux paramètres), mais je suppose que cela n'a pas beaucoup pour la question. (C'est-à-dire pourquoi est-il une mauvaise pratique de déléguer des tests aux méthodes d'assistance, voire du tout?)

  4. En cas de problème, il s'agit d'un test d'intégration, mais je soupçonne que la réponse serait la même pour les tests unitaires.

[~ # ~] Edit [~ # ~]: changé les noms des cas de test pour éviter toute confusion (précédemment, elles étaient appelées test1, test2. En fait, les tests du projet dans les questions ont des noms significatifs, je venais de faire cette simplification moi-même.)

8
Attilio

Comme d'autres ont déjà déclaré: rien de mal à briser une méthode distincte pour éviter les doubles emplois.

Cependant, comme d'autres personnes ont également déclaré: vous voulez que chaque test individuel indique clairement ce qu'il conforme et ce qu'il affirme.

Les noms de test aideront énormément et je suppose que votre code réel a des noms plus significatifs que Test1 et Test2.

Pour rendre le code des tests individuels plus "intelligibles" - c'est-à-dire que cela plus clairement ce que chaque test fait simplement en lisant le code de ce test, vous pouvez rompre votre méthode d'assistance dans ses parties évidentes.

Oui, il sera "dupliquer" ce qui serait autrement confiné à l'aide unique, car vous aurez maintenant plusieurs appels dans chaque test lorsque vous pourriez le faire avec un, mais il vous permet de donner des noms plus significatifs à chaque méthode d'assistance.

// Arrange
someHelper = MakeSomeDependency(arg1, arg2, arg3);
sut = MakeSomeObjectUnderTest(someHelper);

// Act
sut.DoSomething();

// Assert
AssertCommonAsserts(sut, expectedvalue1, expectedvalue2);
Assert.AreEqual(sut.SomeProperty, expectedValue);

Remarque: Je ne suis pas un fan de multiples asserts dans un seul test, mais il y a des cas où un seul "fait" ne peut être vérifié que par plusieurs affirmations et je peux également penser à un certain nombre de scénarios dans lesquels avoir une "garde" affirme. Avant que le "fait" réellement affirmé puisse aider à déboguer les échecs.

Si vous utilisez une langue prenant en charge les paramètres nommés, vous pouvez rendre vos tests encore plus intelligibles en utilisant cette fonctionnalité afin que l'appel de la méthode ne transmet pas simplement les valeurs utilisées, mais également quelles sont les valeurs utilisées.

// Arrange
someHelper = MakeSomeDependency(
    knownNames: arg1, 
    unknownNames: arg2, 
    lookingFor: arg3);
sut = MakeSomeObjectUnderTest(someHelper);

// Act
sut.DoSomething();

// Assert
AssertCommonAsserts(
    objectUnderTest: sut, 
    numberOfItemsFound: expectedvalue1, 
    nameOfItemFound: expectedvalue2);
Assert.AreEqual(sut.SomeProperty, expectedValue);
3
Marjan Venema

Il n'y a rien de mal à structurer votre code de test ainsi que votre code d'entreprise. Le point d'une suite d'essais est qu'il exprime clairement comment le code testé est censé se comporter et qu'il signale une échec si elle ne le fait pas. Avec un refactoring judicieux et des noms bien choisis dans votre code de test, vous pouvez mieux servir cet objectif mieux qu'avec la drudgerie coupée et pâte.

(Cependant, Matthew est tout à fait juste: le code de configuration et de démarrage qui est commun à Tous Tests appartient aux méthodes spéciales du cadre, non dans vos sous-routines personnalisées.)

1
Kilian Foth

Vous devriez envisager d'utiliser un fonctionnement paramétré à l'aide d'un DataProvider pour toutes les entrées valides pour vos étuis de test particuliers, au lieu de votre Haute Aproach. J'utilise des méthodes d'assistance uniquement pour accroître la lisibilité des tests, s'ils ont une configuration complexe dans le test et qui encombreraient la partie arrangement du test.

Ce qu'il pouvait ressembler est:

@RunWith( DataProviderRunner.class )
public class YourClassTest {

    @Test
    @DataProvider( value = { "value11|value12|value13","value21|value22|value23" }, 
                   trimValues = true, splitBy = "\\|" )
    public void test_something(String value1, String value2, String value3) {
       // action part (dependent on values)

       // assertion part (dependent on values)
    }


    @Before
    public void independendSetup() {
       // set up part (independent of value1, value2, value3)
    }

    @After
    public void independendtearDown() {
       // tear down part (independent of values)
    }
}
0
thepacker

Vous devriez absolument factuper le code commun dans les tests unitaires. Le code de test unitaire désordonné et mal structuré conduit à des tests difficiles à comprendre et difficiles à maintenir, peu importe la beauté du code des tests.

Cela dit, dans ce cas particulier si vous utilisiez le cadre de test .NET Nunit, cela ne serait pas nécessaire du tout - vous allumez l'assistant en une méthode de test et utilisez l'attribut TestCase pour obtenir le cadre Pour l'appeler plusieurs fois avec différents ensembles de paramètres. Je serais surpris s'il n'y avait pas quelque chose d'équivalent disponible pour Java et d'autres langues.

Ainsi, les tests de structure sont donc bien, mais assurez-vous également d'avoir appris les capacités de la structure de test car elle pourrait être en mesure de vous aider.

0
Matthew Walton