web-dev-qa-db-fra.com

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

Devrais-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 des 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 list sera créé une fois par test, quel que soit le lieu où je le déclare. Cela signifie également qu'il n'y a pas de dépendance temporelle entre les tests. Il semble donc que l’utilisation de setUp () ne présente aucun avantage. Cependant, JUnit FAQ contient de nombreux exemples qui initialisent une collection vide dans setUp (), je suppose donc qu'il doit y avoir une raison.

109
Craig P. Motlin

Si vous vous posez des questions sur les exemples de la FAQ JUnit, tels que le modèle de test de base , je pense que la meilleure pratique à suivre est que la classe under test doit être instancié dans votre méthode setUp (ou dans une méthode de test).

Lorsque les exemples JUnit créent une ArrayList dans la méthode setUp, ils testent tous le comportement de cette ArrayList, avec des cas tels que testIndexOutOfBoundException, testEmptyCollection, etc. Le point de vue d’une personne qui écrit 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 objet dans setUp ou dans une méthode de test, de manière à pouvoir obtenir un résultat raisonnable si vous le supprimez plus tard.

D'autre part, 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 - c'est juste une partie Dans ce cas, vous pouvez sans risque supposer que cela fonctionne comme prévu, donc son initialisation dans la déclaration ne posera pas de problème.

Pour ce que ça vaut, je travaille sur une base de code développée par TDD, vieille de plusieurs années et assez longue. Nous initialisons habituellement les choses dans leurs déclarations dans le code de test, et depuis un an et demi que je participe à ce projet, cela n'a jamais posé de problème. Il existe donc au moins certaines preuves anecdotiques selon lesquelles il est raisonnable de le faire.

92
Moss Collum

J'ai commencé à creuser moi-même et j'ai trouvé un avantage potentiel à utiliser setUp(). Si des exceptions sont générées lors de l'exécution de setUp(), JUnit imprimera une trace de pile très utile. Par ailleurs, si une exception est levée lors de la construction de l'objet, le message d'erreur indique simplement que JUnit n'a pas pu instancier le cas de test et que le numéro de ligne où l'échec s'est produit ne s'affiche pas, probablement parce que JUnit utilise la réflexion pour instancier le test. Des classes.

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

42
Craig P. Motlin

En plus de la réponse de Alex B.

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

JUnit commence par créer des instances de testClass pour chaque méthode de test et commence à exécuter les tests après la création de chaque instance. Avant d'exécuter la méthode de test, sa méthode de configuration est exécutée, dans laquelle un certain état peut être préparé.

Si l'état de la base de données était créé dans le constructeur, toutes les instances instancieraient l'état de la base de données l'une après l'autre, avant l'exécution de chaque test. À partir du deuxième test, les tests seraient exécutés avec un état sale.

Cycle de vie des JUnits:

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

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

  • Création d'une nouvelle instance: 5718203
  • Création d'une nouvelle instance: 5947506
  • Configuration: 5718203
  • TestOne: 5718203
  • Setup: 5947506
  • TestTwo: 5947506
18
Jurgen Hannaert

Dans JUnit 4:

  • Pour la classe sous test , initialisez-la dans un @Before méthode, pour attraper les échecs.
  • Pour les autres classes , initialisez dans la déclaration ...
    • ... pour abréger et pour marquer les champs final, exactement comme indiqué dans la question,
    • ... sauf si c'est une initialisation complexe qui pourrait échouer, auquel cas utilisez @Before, pour attraper les échecs.
  • Pour l'état global (en particulier une initialisation lente , comme une base de données), utilisation @BeforeClass, mais soyez prudent des dépendances entre les tests.
  • L'initialisation d'un objet utilisé dans un seul test doit bien entendu être effectuée dans la méthode de test elle-même.

Initialisation dans un @Before méthode ou méthode de test vous permet d’obtenir un meilleur rapport d’erreurs sur les échecs. Ceci est particulièrement utile pour l'instanciation de la classe en cours de test (que vous pourriez endommager), mais également pour appeler des systèmes externes, tels que l'accès au système de fichiers ("fichier introuvable") ou la connexion à une base de données ("connexion refusée").

Il est acceptable d'avoir un standard simple et de toujours utiliser @Before (erreurs claires mais verbeuses) ou toujours initialiser dans la déclaration (concis, mais donne des erreurs déroutantes), car les règles de codage complexes sont difficiles à suivre et que ce n’est pas grave.

Initialisation dans setUp est une relique de JUnit 3, où toutes les instances de test ont été initialisées avec impatience, ce qui pose des problèmes (vitesse, mémoire, épuisement des ressources) si vous effectuez une initialisation coûteuse. Par conséquent, la meilleure pratique consistait à 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 enfouissent l'enterrement, notamment de Craig P. Motlin (question elle-même et auto-réponse), Moss Collum (classe sous test) et dsaff.

10
Nils von Barth

Dans JUnit 3, vos initialiseurs de champ seront exécutés une fois par méthode de test avant l'exécution de tout test. Tant que vos valeurs de champ sont faibles en mémoire, prennent peu de temps à configurer et n’affectent pas l’état global, l’utilisation d’initialiseurs de champ convient techniquement. Cependant, si cela ne tient pas, vous risquez de perdre beaucoup de temps ou de mémoire lors de la configuration de 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 les valeurs de champ dans la méthode setUp (), où elles sont toujours sécurisées, même lorsque cela n'est pas strictement nécessaire.

Notez que dans JUnit 4, l'initialisation des objets de test a lieu juste avant l'exécution du test. L'utilisation d'initialiseurs de champs est donc plus sûre et le style recommandé.

7
dsaff

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

6
amit

Je préfère d'abord la lisibilité qui, le plus souvent, n'utilise pas la méthode d'installation. Je fais une exception lorsqu'une opération de configuration de base prend beaucoup de temps et est répétée dans chaque test.
À ce stade, je déplace cette fonctionnalité dans une méthode de configuration à l'aide du @BeforeClass annotation (optimiser plus tard).

Exemple d'optimisation avec le @BeforeClass méthode d'installation: j'utilise dbunit pour certains tests fonctionnels de base de données. La méthode de configuration est chargée de mettre la base de données 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 de configuration annotée avec @BeforeClass puis exécutez 10 à 20 tests sur le même ensemble de données, par opposition à un nouveau chargement/initialisation de la base de données dans chaque test.

L'utilisation de Junit 3.8 (étendre TestCase comme indiqué dans votre exemple) nécessite l'écriture d'un peu plus de code que simplement l'ajout d'une annotation, mais l'option "exécuter une fois avant la configuration de la classe" est toujours possible.

5
Alex B

Étant donné que chaque test est exécuté indépendamment, avec une nouvelle instance de l’objet, il n’ya guère de raison que l’objet Test ait un état interne autre que celui partagé entre setUp() et un test individuel et tearDown() . C'est une des raisons (en plus des raisons données par d'autres) qu'il est bon d'utiliser la méthode setUp().

Remarque: il est déconseillé à un objet de test JUnit de conserver un état statique! Si vous utilisez une variable statique dans vos tests à des fins autres que de suivi ou de diagnostic, vous invalidez une partie de l'objectif de JUnit, à savoir que les tests peuvent (peuvent) être exécutés dans n'importe quel ordre, chaque test exécuté avec un état frais et propre.

L'utilisation de setUp() présente l'avantage de ne pas devoir copier-coller du code d'initialisation dans chaque méthode de test et de ne pas disposer de code de configuration de test dans le constructeur. Dans votre cas, il y a peu de différence. Il vous 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 triviale. Cependant, comme vous et d'autres l'ont fait remarquer, tout ce qui peut éventuellement jeter un Exception devrait être fait dans setUp() afin que vous obteniez le vidage de la pile de diagnostics en cas d'échec.

Dans votre cas, où vous créez simplement une liste vide, je procéderais de la même façon que vous suggérez: Attribuez la nouvelle liste au point de déclaration. Surtout parce que de cette façon, vous avez la possibilité de le marquer final si cela a du sens pour votre classe de test.

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

  • l'objet à tester doit être initialisé dans la méthode de configuration, car nous pouvons définir des paramètres. Bien sûr, nous ne pouvons pas définir quelque chose maintenant, mais nous pourrions le faire plus tard. L'instanciation dans la méthode init faciliterait les changements.

  • les dépendances de l’objet testé si elles sont moquées ne doivent même pas être instanciées par vous-même: aujourd’hui les frameworks simulés peuvent l’instancier par réflexion.

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

public class SomeTest {

    Some some; //instance under test
    static final String GENERIC_ID = "123";
    static final String 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 String GENERIC_ID = "123";
    static final String 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()
         ...
    }
}
0
davidxxx