2009-11-09 4 views
0

Supposons que vous avez cette coquille d'une classe:C#: Comment écrire des cas de test pour les constructeurs et les surcharges constructeurs?

public class Number 
{ 
    private int value; 

    public Number() 
     : this(0) {} 

    public Number(int initialValue) 
     : this(initialValue, 0, 100) {} 

    public Number(int initialValue, int minimumValue, int maximumValue) 
    { 
     if (minimumValue > maximumValue) 
      throw new ArgumentException("Minimum cannot be greater than maximum", "minimumValue"); 

     MinValue = minimumValue; 
     MaxValue = maximumValue; 
     Value = initialValue; 
    } 

    public int MinValue { get; private set; } 
    public int MaxValue { get; private set; } 

    public int Value 
    { 
     get { return value; } 
     set 
     { 
      if (value < MinValue) 
       value = MinValue; 
      if (value > MaxValue) 
       value = MaxValue; 

      this.value = value; 
     } 
    } 
} 

Voulez-vous écrire des tests pour cette classe et si oui, comment voulez-vous les écrire? Je pense en particulier aux constructeurs. Comme, auriez-vous un test qui a créé un Number en utilisant le constructeur par défaut et en vérifiant que la valeur était 0, minvalue était 0 et maxvalue était 100? Ou serait-ce sur les spécifications? Ou est-ce vraiment pas, puisque d'autres pourraient dépendre que les valeurs par défaut n'ont pas changé par accident? Souhaitez-vous écrire un test pour chaque constructeur, ou juste celui par défaut, puisque vous savez qu'il enchaîne tous les autres.

+0

Je suppose que la classe est également appelé NumberParameter, de sorte que vous avez 3 surcharges de constructeur (ressemble à une faute de frappe)? – Mathias

+0

Oh, typo: p va réparer! – Svish

+1

Pour tester, vous ne voulez pas de valeurs codées en dur qui nécessitent une connaissance omnisciente du système. Faites vos valeurs par défaut readonly statique de paramètres par défaut contre. –

Répondre

3

Je suis complètement passé de l'approche classique pour TDD à la BDD (Behaviour Driven Design) plus moderne et logique. Dans le cas de votre classe Number, je voudrais écrire les spécifications BDD suivantes (Notez que la syntaxe ci-dessous est fait avec SubSpec, qui repose sur xUnit.NET):

public void Parameterless_constructor_initializes_all_defaults_properly() 
{ 
    // State 
    Number number = null; 

    // Context 
    "Given a null context".Context(() => {}); 

    // Concern 
    "when creating a new Number with no parameters".Do(() => { number = new Number(); }); 

    // Observations 
    "the Value property should contain the default value 0".Assert(() => Assert.Equal(0, number.value)); 
    "the MinValue property should be 0".Assert(() => Assert.Equal(0, number.MinValue)); 
    "the MaxValue property should be 100".Assert(() => Assert.Equal(100, number.MaxValue)); 
} 

public void Single_parameter_constructor_initializes_all_defaults_and_initial_value_properly() 
{ 
    // State 
    Number number = null; 

    // Context 
    "Given a null context".Context(() => {}); 

    // Concern 
    "when creating a new Number with the initial value".Do(() => { number = new Number(10); }); 

    // Observations 
    "the Value property should contain the value 10".Assert(() => Assert.Equal(10, number.value)); 
    "the MinValue property should be 0".Assert(() => Assert.Equal(0, number.MinValue)); 
    "the MaxValue property should be 100".Assert(() => Assert.Equal(100, number.MaxValue)); 
} 

public void Full_constructor_initializes_all_values_properly() 
{ 
    // State 
    Number number = null; 

    // Context 
    "Given a null context".Context(() => {}); 

    // Concern 
    "when creating a new Number with the initial, min, and max values".Do(() => { number = new Number(10, 1, 50); }); 

    // Observations 
    "the Value property should contain the value 10".Assert(() => Assert.Equal(10, number.value)); 
    "the MinValue property should be 1".Assert(() => Assert.Equal(1, number.MinValue)); 
    "the MaxValue property should be 50".Assert(() => Assert.Equal(50, number.MaxValue)); 
} 

En outre, je remarque que vous avez aussi un possible scénario exceptionnel pour votre constructeur complet, lorsque la valeur min est supérieure à la valeur max. Vous voudriez également vérifier le bon comportement dans ce cas exceptionnel:

public void Full_constructor_throws_proper_exception_when_minvalue_greater_than_maxvalue() 
{ 
    // State 
    Number number = null; 
    Exception expectedEx = null; 

    // Context 
    "Given a null context".Context(() => {}); 

    // Concern 
    "when creating a new Number with inverted min and max values".Do(
     () => 
     { 
      try { number = new Number(10, 50, 1); } 
      catch (Exception ex) { expectedEx = ex } 
     } 
    ); 

    // Observations 
    "an exception should be thrown".Assert(() => Assert.NotNull(expectedEx)); 
    "the exception should be an ArgumentException".Assert(() => Assert.IsType<ArgumentException>(expectedEx)); 
} 

Les spécifications ci-dessus devraient vous donner 100% de couverture de test. Ils produisent également un très bon rapport logique lisible par un humain lorsqu'ils sont exécutés avec xunit.net et produisent le rapport par défaut.

+0

À quoi sert le contexte nul? – Svish

+0

Eh bien, il n'y a vraiment pas de contexte spécifique. Le comportement s'exécute correctement dans n'importe quel contexte, il ne nécessite rien de spécial. Si vous vous êtes basé sur un contexte spécifique ... par exemple, vous avez demandé qu'une ou plusieurs dépendances soient créées et injectées, alors vous les prépareriez dans le contexte. Vous pouvez réécrire les tests pour supprimer complètement la partie de contexte null, et simplement construire la classe Number dans le contexte, et éliminer la partie .Do(). Vous pourriez vous retrouver avec: "Étant donné un nouveau nombre initialisé sans paramètre, la propriété Value doit contenir la valeur par défaut 0". – jrista

+0

J'ai jeté dans le contexte nul pour démontrer l'intention complète de BDD et comment utiliser SubSpec. ;-) Désolé si cela a causé de la confusion. – jrista

0

Utiliser nunit.

Créez un test qui crée un objet pour chaque constructeur. Utilisez Assert.AreEqual pour vous assurer que tous les objets sont égaux (vous devez remplacer Equals pour les classes comme celle-ci). Pour être sûr, les assertions négatives assert l'assertion Assert.AreSame.

Ensuite, testez chaque propriété pour obtenir la valeur correcte. Si votre classe était plus compliquée et que vous vouliez être encore plus prudent, vous pourriez alors définir toutes les valeurs sur des nombres aléatoires uniques et affirmer que les propriétés sont initialisées correctement à partir d'un ensemble de données aléatoires.

1

Je suppose que vous avez plusieurs constructeurs pour une raison - essayez de tester le scénario et non que la classe a été initialisée selon une règle.
Par exemple, si vous utilisez le constructeur par défaut pour créer une classe pour le test de calcul à la volée, et non le fait que le constructeur par défaut ait une valeur définie. Mon point est que vous ne devriez pas avoir de surcharges que vous n'utilisez pas (sauf si vous développez API) alors pourquoi ne pas tester le cas d'utilisation au lieu du constructeur.

0

J'écrirais un test unitaire pour chaque constructeur, en vérifiant que les valeurs minimum et maximum sont définies correctement. Je ferais ceci pour m'assurer que si je change le code d'un des constructeurs plus tard, mes tests me disent ce qui a changé où.
Je voudrais également extraire les valeurs par défaut min et max dans une constante, probablement, de sorte que le test ressemblerait à Assert.AreEqual (DefaultMinimum, myNumber.MinValue).
Je voudrais faire un test en vérifiant qu'une min/max invalide jette une exception.
Et je renommer cette classe « BoundedNumber » ou quelque chose dans ce sens :)

0

OK, je suis vraiment répondre à la question juste en dessous du code liste (plutôt que celui du titre) ...

Je pense que la valeur principale de cette classe est dans sa propriété (jeu de mots non prévu) Value. Par conséquent, c'est cette propriété plutôt que les constructeurs qui devrait faire l'objet de tests unitaires. Si vous écrivez des tests unitaires après avoir écrit les trois constructeurs et rendu ces tests trop restrictifs (sur-spécification), vous risquez de vous retrouver avec une suite de tests fragiles et difficiles à maintenir.

0

Tous les tests pour les constructeurs sont similaires, car les choses que les constructeurs font sont similaires par définition. J'ai donc écrit une bibliothèque de test simple qui aide à écrire des tests déclaratives pour les constructeurs: How to Easily Test Validation Logic in Constructors in C#

Voici un exemple dans lequel je suis en train de sept cas de test sur un constructeur d'une classe:

[TestMethod] 
public void Constructor_FullTest() 
{ 

    IDrawingContext context = new Mock<IDrawingContext>().Object; 

    ConstructorTests<Frame> 
     .For(typeof(int), typeof(int), typeof(IDrawingContext)) 
     .Fail(new object[] { -3, 5, context }, typeof(ArgumentException), "Negative length") 
     .Fail(new object[] { 0, 5, context }, typeof(ArgumentException), "Zero length") 
     .Fail(new object[] { 5, -3, context }, typeof(ArgumentException), "Negative width") 
     .Fail(new object[] { 5, 0, context }, typeof(ArgumentException), "Zero width") 
     .Fail(new object[] { 5, 5, null }, typeof(ArgumentNullException), "Null drawing context") 
     .Succeed(new object[] { 1, 1, context }, "Small positive length and width") 
     .Succeed(new object[] { 3, 4, context }, "Larger positive length and width") 
     .Assert(); 

} 
Questions connexes