Meilleure pratique: Initialiser les champs de classe JUnit dans setUp () ou à la déclaration?

Dois-je initialiser les champs de classe à la déclaration comme ceci?

public class SomeTest extends TestCase { private final List list = new ArrayList(); public void testPopulateList() { // Add stuff to the list // Assert the list contains what I expect } } 

Ou dans setUp () comme ça?

 public class SomeTest extends TestCase { private List list; @Override protected void setUp() throws Exception { super.setUp(); this.list = new ArrayList(); } public void testPopulateList() { // Add stuff to the list // Assert the list contains what I expect } } 

J’ai tendance à utiliser le premier formulaire car il est plus concis et me permet d’utiliser les champs finaux. Si je n’ai pas besoin d’utiliser la méthode setUp () pour la configuration, devrais-je toujours l’utiliser, et pourquoi?

Clarification: JUnit instanciera la classe de test une fois par méthode de test. Cela signifie que la list sera créée une fois par test, quel que soit l’endroit où je le déclare. Cela signifie également qu’il n’y a pas de dépendance temporelle entre les tests. Il semble donc qu’il n’y ait aucun avantage à utiliser setUp (). Cependant, la FAQ de JUnit contient de nombreux exemples d’initialisation d’une collection vide dans setUp (), alors je pense qu’il doit y avoir une raison.

Si vous vous interrogez spécifiquement sur les exemples de la FAQ JUnit, tels que le modèle de test de base , je pense que la meilleure pratique est que la classe testée doit être instanciée dans votre méthode setUp (ou dans une méthode de test). .

Lorsque les exemples JUnit créent un ArrayList dans la méthode setUp, ils testent tous le comportement de cette ArrayList, avec des cas tels que testIndexOutOfBoundException, testEmptyCollection, etc. La perspective est que quelqu’un écrive un cours et s’assure que cela fonctionne correctement.

Vous devriez probablement faire la même chose lorsque vous testez vos propres classes: créez votre object dans setUp ou dans une méthode de test, de sorte que vous puissiez obtenir une sortie raisonnable si vous la cassez plus tard.

D’un autre côté, si vous utilisez une classe de collection Java (ou une autre classe de bibliothèque, d’ailleurs) dans votre code de test, ce n’est probablement pas parce que vous voulez le tester – il ne s’agit que d’une partie du assembly de test. Dans ce cas, vous pouvez supposer que cela fonctionne comme prévu, l’initialiser dans la déclaration ne posera donc aucun problème.

Pour ce que cela vaut, je travaille sur une base de code relativement développée, vieille de plusieurs années, développée par TDD. Nous initialisons habituellement les choses dans leurs déclarations dans le code de test, et dans l’année et demie que je participe à ce projet, cela n’a jamais posé de problème. Donc, il y a au moins quelques preuves anecdotiques que c’est une chose raisonnable à faire.

J’ai commencé à me creuser et j’ai trouvé un avantage potentiel de l’utilisation de setUp() . Si des exceptions sont setUp() pendant l’exécution de setUp() , JUnit imprimera une trace de stack très utile. D’un autre côté, si une exception est levée pendant la construction de l’object, le message d’erreur indique simplement que JUnit n’a pas pu instancier le scénario de test et que le numéro de ligne n’a pas eu lieu, probablement parce que JUnit utilise la reflection pour instancier le test. Des classes.

Rien de tout cela ne s’applique à l’exemple de création d’une collection vide, car cela ne lancera jamais, mais c’est un avantage de la méthode setUp() .

En plus de la réponse d’Alex B.

Il est même nécessaire d’utiliser la méthode setUp pour instancier des ressources dans un certain état. Faire ceci dans le constructeur n’est pas seulement une question de minutage, mais à cause de la façon dont JUnit exécute les tests, chaque état de test serait effacé après avoir exécuté un.

JUnit crée d’abord des instances de la classe testClass pour chaque méthode de test et lance l’exécution des tests après la création de chaque instance. Avant d’exécuter la méthode de test, sa méthode d’installation est exécutée, dans laquelle certains états peuvent être préparés.

Si l’état de la firebase database était créé dans le constructeur, toutes les instances instancieraient l’état de la firebase database immédiatement après l’exécution de chaque test. Au deuxième test, les tests se déroulaient dans un état sale.

Cycle de vie des unités J:

  1. Créer une instance de classe de test différente pour chaque méthode de test
  2. Répétez pour chaque instance de testclass: appelez setup + appelez la méthode de test

Avec quelques loggings dans un test avec deux méthodes de test, vous obtenez: (le nombre est le hashcode)

  • Créer une nouvelle instance: 5718203
  • Créer une nouvelle instance: 5947506
  • Configuration: 5718203
  • TestOne: 5718203
  • Setup: 5947506
  • TestTwo: 5947506

Dans JUnit 4:

  • Pour la classe sous test , initialisez dans une méthode @Before pour intercepter les échecs.
  • Pour les autres classes , initialiser dans la déclaration …
    • … pour la concision et pour marquer les champs final , exactement comme indiqué dans la question,
    • … sauf en cas d’ initialisation complexe qui pourrait échouer, auquel cas utilisez @Before pour intercepter les défaillances.
  • Pour l’état global (en particulier l’ initialisation lente , comme une firebase database), utilisez @BeforeClass , mais @BeforeClass attention aux dépendances entre les tests.
  • L’initialisation d’un object utilisé dans un seul test doit bien entendu être effectuée dans la méthode de test elle-même.

L’initialisation dans une méthode ou une méthode de test @Before permet d’obtenir un meilleur rapport d’erreurs sur les échecs. Ceci est particulièrement utile pour instancier la classe sous test (que vous pourriez casser), mais est également utile pour appeler des systèmes externes, comme l’access au système de fichiers (“fichier introuvable”) ou la connexion à une firebase database (“connexion refusée”).

Il est acceptable d’avoir une norme simple et d’utiliser toujours @Before (erreurs claires mais verbeuses) ou d’initialiser toujours dans la déclaration (concise mais donne des erreurs confuses), car les règles de codage complexes sont difficiles à suivre.

L’initialisation dans setUp est une relique de JUnit 3, où toutes les instances de test ont été initialisées avec empressement, ce qui pose des problèmes (vitesse, mémoire, épuisement des ressources) si vous effectuez une initialisation coûteuse. La meilleure pratique consistait donc à effectuer une initialisation coûteuse dans setUp , qui n’était exécutée que lors de l’exécution du test. Cela ne s’applique plus, il est donc beaucoup moins nécessaire d’utiliser setUp .

Ceci résume plusieurs autres réponses qui enterrent la lede, notamment par Craig P. Motlin (question elle-même et auto-réponse), Moss Collum (classe sous test), et dsaff.

Dans JUnit 3, vos initialiseurs de champs seront exécutés une fois par méthode de test avant l’exécution des tests . Tant que les valeurs de vos champs sont faibles en mémoire, prenez peu de temps de réglage et n’affectez pas l’état global, l’utilisation des initialiseurs de champs est techniquement correcte. Toutefois, si ceux-ci ne sont pas conservés, vous risquez de perdre beaucoup de temps ou de mémoire avant de configurer vos champs avant l’exécution du premier test, voire de manquer de mémoire. Pour cette raison, de nombreux développeurs définissent toujours des valeurs de champ dans la méthode setUp (), où elles sont toujours sûres, même si cela n’est pas ssortingctement nécessaire.

Notez que dans JUnit 4, l’initialisation des objects de test se produit juste avant le test, l’utilisation des initialiseurs de champs est donc plus sûre et le style recommandé.

Dans votre cas (création d’une liste), il n’y a pas de différence dans la pratique. Mais en général, il est préférable d’utiliser setUp (), car cela aidera Junit à signaler les exceptions correctement. Si une exception se produit dans le constructeur / initialiseur d’un test, il s’agit d’un échec de test. Cependant, si une exception se produit lors de l’installation, il est naturel de la considérer comme un problème lors de la configuration du test, et junit le signale correctement.

Je préfère d’abord la lisibilité, qui le plus souvent n’utilise pas la méthode d’installation. Je fais une exception quand une opération de configuration de base prend beaucoup de temps et se répète dans chaque test.
À ce stade, je déplace cette fonctionnalité dans une méthode d’installation à l’aide de l’annotation @BeforeClass (optimise ultérieurement).

Exemple d’optimisation utilisant la méthode d’installation @BeforeClass : J’utilise dbunit pour certains tests fonctionnels de firebase database. La méthode de configuration est chargée de placer la firebase database dans un état connu (très lent … 30 secondes – 2 minutes en fonction de la quantité de données). Je charge ces données dans la méthode d’installation annotée avec @BeforeClass , puis exécute 10 à 20 tests sur le même dataset plutôt que de recharger / initialiser la firebase database dans chaque test.

L’utilisation de Junit 3.8 (extension de TestCase comme illustré dans votre exemple) nécessite d’écrire un peu plus de code que d’append simplement une annotation, mais le “lancer une fois avant l’installation de la classe” est toujours possible.

Comme chaque test est exécuté indépendamment, avec une nouvelle instance de l’object, il n’y a pas beaucoup de points sur l’object Test ayant un état interne autre que celui partagé entre setUp() et un test individuel et tearDown() . C’est une des raisons (en plus des raisons invoquées par d’autres) que c’est bien d’utiliser la méthode setUp() .

Remarque: Il est déconseillé qu’un object de test JUnit conserve son état statique! Si vous utilisez des variables statiques dans vos tests à des fins autres que le suivi ou le diagnostic, vous invalidez une partie de l’objective de JUnit, à savoir que les tests peuvent (et peuvent) être exécutés dans n’importe quel ordre, chaque test étant exécuté avec un état frais et propre.

L’avantage d’utiliser setUp() est que vous n’avez pas à couper et coller le code d’initialisation dans chaque méthode de test et que vous n’avez pas de code de configuration de test dans le constructeur. Dans votre cas, il y a peu de différence. Il suffit de créer une liste vide en toute sécurité lorsque vous l’affichez ou dans le constructeur car il s’agit d’une initialisation sortingviale. Cependant, comme vous et d’autres l’avez souligné, tout ce qui peut éventuellement setUp() une Exception doit être fait dans setUp() afin que vous puissiez obtenir le vidage de la stack de diagnostic en cas d’échec.

Dans votre cas, où vous ne faites que créer une liste vide, je ferais la même chose que vous proposez: assignez la nouvelle liste au sharepoint déclaration. Surtout parce que de cette façon, vous avez la possibilité de marquer le résultat final si cela a du sens pour votre classe de test.

  • Les valeurs constantes (utilisations dans les fixtures ou les assertions) doivent être initialisées dans leurs déclarations et final (comme jamais changer)

  • l’object à tester doit être initialisé dans la méthode de configuration car nous pouvons mettre les choses en marche. Bien sûr, nous ne pouvons pas mettre quelque chose maintenant, mais nous pourrions le définir plus tard. L’instanciation de la méthode init faciliterait les modifications.

  • les dépendances de l’object testé si elles sont bafouées, ne doivent même pas être instanciées par vous-même: aujourd’hui, les simulacres de framework peuvent l’instancier par reflection.

Un test sans dépendance à simuler pourrait ressembler à:

 public class SomeTest { Some some; //instance under test static final Ssortingng GENERIC_ID = "123"; static final Ssortingng PREFIX_URL_WS = "http://foo.com/ws"; @Before public void beforeEach() { some = new Some(new Foo(), new Bar()); } @Test public void populateList() ... } } 

Un test avec des dépendances à isoler pourrait ressembler à:

 @RunWith(org.mockito.runners.MockitoJUnitRunner.class) public class SomeTest { Some some; //instance under test static final Ssortingng GENERIC_ID = "123"; static final Ssortingng PREFIX_URL_WS = "http://foo.com/ws"; @Mock Foo fooMock; @Mock Bar barMock; @Before public void beforeEach() { some = new Some(fooMock, barMock); } @Test public void populateList() ... } }