web-dev-qa-db-fra.com

Comment tester les constructeurs unitaires

J'ai une classe à laquelle j'ajoute des tests unitaires. La classe a plusieurs constructeurs qui prennent différents types et les convertissent en une forme canonique, qui peut ensuite être convertie en d'autres types.

public class Money {
    public Money(long l) {
        this.value = l;
    }

    public Money(String s) {
        this.value = toLong(s);
    }

    public long getLong() {
        return this.value;
    }

    public String getString() {
        return toString(this.value);
    }
}

En réalité, il en accepte deux autres et les convertit.

J'essaie de trouver la meilleure façon de tester ces constructeurs.

Devrait-il y avoir un test par constructeur et type de sortie:

@Test
public void longConstructor_getLong_MatchesValuePassedToConstructor() {
    final long value = 1.00l;

    Money m = new Money(value);
    long result = m.getLong();

    assertEquals(value, result);
}

Cela conduit à de nombreux tests différents. Comme vous pouvez le voir, j'ai du mal à les nommer.

Devrait-il y avoir plusieurs assertions:

@Test
public void longConstructor_outputsMatchValuePassedToConstructor() {
    final long longValue = 1.00l;
    final String stringResult = "1.00";

    Money m = new Money(longValue);

    assertEquals(longValue, m.getLong());
    assertEquals(stringResult, m.getString());
}

Cela a plusieurs assertions, ce qui me met mal à l'aise. Il teste également getString (et par proxy toString) mais ne l'indique pas dans le nom du test. Les nommer est encore plus difficile.

Suis-je en train de me tromper complètement en me concentrant sur les constructeurs. Dois-je simplement tester les méthodes de conversion? Mais alors le test suivant manquera la méthode toLong.

@Test
public void getString_MatchesValuePassedToConstructor() {
    final long value = 1.00;
    final String expectedResult = "1.00";

    Money m = new Money(value);
    String result = m.getLong();
    assertEquals(expectedResult, result);
}

Il s'agit d'une classe héritée et je ne peux pas changer la classe d'origine.

36
ICR

Il semble que vous ayez un moyen canonique d'obtenir la valeur "brute" (toLong dans ce cas) - alors testez simplement que tous les constructeurs sont corrects lorsque vous récupérez que valeur . Ensuite, vous pouvez tester d'autres méthodes (telles que getString()) basées sur un seul constructeur, car vous savez qu'une fois les différents constructeurs terminés, ils laissent tous l'objet dans le même état.

Cela suppose des tests en boîte blanche - c'est-à-dire que vous savez que toLong est vraiment un simple reflet de l'état interne, il est donc normal de tester cela + un constructeur dans un test.

16
Jon Skeet

Le résultat attendu d'un test constructeur est: ne instance a été créée

En suivant cette idée, vous pouvez limiter le travail dans les tests de constructeur à une pure instanciation:

@Test public void testMoneyString() {
    try {
      new Money("0");
      new Money("10.0");
      new Money("-10.0");
    } catch (Exception e) {
      fail(e.getMessage());
    }
}

@Test public void testMoneyStringIllegalValue() {
    try {
      new Money(null);
      fail("Exception was expected for null input");
    } catch (IllegalArgumentException e) {          
    }

    try {
      new Money("");
      fail("Exception was expected for empty input");
    } catch (IllegalArgumentException e) {          
    }

    try {
      new Money("abc");
      fail("Exception was expected for non-number input");
    } catch (IllegalArgumentException e) {          
    }

}

Un test pour vérifier si le travail de conversion peut être attribué aux getters.

14
Andreas_D

Un test par constructeur me semble le plus approprié. N'ayez pas peur d'utiliser des noms longs, élaborés et verbeux pour vos méthodes de test, cela les rend évidentes et descriptives.

@Test
public void moneyConstructorThatTakesALong {
11
skaffman

Je pense que vous passez trop de temps à y penser. Toutes vos options fonctionnent très bien, choisissez simplement celle que vous préférez. N'oubliez pas que le but est de vous donner l'assurance que le code fonctionnera comme prévu/prévu. Chacun de ces scénarios fournira cela.

Personnellement, dans un cas aussi simple, j'irais avec un seul cas de test qui valide les constructeurs. Il élimine le besoin de noms de méthode excessifs et plutôt lourds.

3
Robin

Ce n'est pas une mauvaise idée de tester les constructeurs juste pour s'assurer que la prise dans les données est requise, mais si vous n'aimez vraiment pas les assertions multiples, divisez-les et nommez la méthode avec ce qu'ils font, par exemple: CanContructorAcceptString ou: CanConstructorAcceptNonLongStringValue

Quelque chose comme ca.

2
RMT

Vous avez plusieurs entrées (via les constructeurs) et plusieurs sorties (via différentes méthodes getX ()). Mais le nombre de membres qu'il possède en interne semble être inférieur (dans votre exemple, 1 valeur longue). Ne serait-il pas plus facile de tester d'abord les différentes entrées en créant x différents objets en utilisant les différents constructeurs x. Ensuite, vous pouvez vérifier si elles sont toutes égales, en utilisant une méthode equals () implémentée. Cela peut être fait dans une seule méthode d'essai.

Ensuite, vous pouvez vérifier les méthodes getter possibles une par une sans utiliser tous les différents constructeurs.

Bien sûr, cela vous oblige à implémenter (un test séparé) la méthode equals.

Dans votre exemple, je créerais les cas de test suivants:

@Test
public void testEquals() {
    Money m1 = new Money(1);
    Money m2 = new Money(1);
    Money m3 = new Money(2);

    assertEquals(m1, m2);
    assertEquals(m2, m1);
    assertNotEquals(m1, m3);
    assertNotEquals(m3, m1);
    assertNotEquals(m1, null);
}

private void testConstructors(long lValue, String sValue) {
    Money m1 = new Money(lValue);
    Money m2 = new Money(sValue);

    assertEquals(m1, m2);
}

@Test
public void testConstructorsPositive() {
    testConstructors(1, "1");
}

@Test
public void testConstructorsNegative() {
    testConstructors(-1, "-1");
}

@Test
public void testConstructorsZero() {
    testConstructors(0, "0");
}

@Test
public void testGet...() { /* etc... */ }
0
Marc