web-dev-qa-db-fra.com

Il devrait tester l'unité des résultats attendus être hardcoded?

Les résultats attendus d'un test d'unité sont-ils corrigés ou peuvent-ils dépendre des variables initialisées? Les résultats codés ou calculés augmentent-ils le risque d'introduire des erreurs dans le test de l'unité? Y a-t-il d'autres facteurs que je n'ai pas pris en compte?

Par exemple, lequel de ces deux est un format plus fiable?

[TestMethod]
public void GetPath_Hardcoded()
{
    MyClass target = new MyClass("fields", "that later", "determine", "a folder");
    string expected = "C:\\Output Folder\\fields\\that later\\determine\\a folder";
    string actual = target.GetPath();
    Assert.AreEqual(expected, actual,
        "GetPath should return a full directory path based on its fields.");
}

[TestMethod]
public void GetPath_Softcoded()
{
    MyClass target = new MyClass("fields", "that later", "determine", "a folder");
    string expected = "C:\\Output Folder\\" + string.Join("\\", target.Field1, target.Field2, target.Field3, target.Field4);
    string actual = target.GetPath();
    Assert.AreEqual(expected, actual,
        "GetPath should return a full directory path based on its fields.");
}

Edit 1 : En réponse à la réponse de DXM, option 3 une solution préférée?

[TestMethod]
public void GetPath_Option3()
{
    string field1 = "fields";
    string field2 = "that later";
    string field3 = "determine";
    string field4 = "a folder";
    MyClass target = new MyClass(field1, field2, field3, field4);
    string expected = "C:\\Output Folder\\" + string.Join("\\", field1, field2, field3, field4);
    string actual = target.GetPath();
    Assert.AreEqual(expected, actual,
        "GetPath should return a full directory path based on its fields.");
}
30
Hand-E-Food

Je pense que la valeur attendue a été calculée dans les cas de test plus robustes et flexibles. En utilisant également de bons noms de variables dans l'expression qui calculent le résultat attendu, il est beaucoup plus clair que le résultat attendu est venu de la première place.

Cela dit, dans votre exemple spécifique, je ne ferais pas confiance à la méthode "codée soft", car elle utilise votre SUT (système sous test) comme entrée de vos calculs. S'il y a un bogue dans MyClass où les champs ne sont pas correctement stockés, votre test passera réellement parce que votre calcul de valeur attendue utilisera la mauvaise chaîne comme cible.gepath ().

Ma suggestion serait de calculer la valeur attendue où cela a du sens, mais assurez-vous que le calcul ne dépend de aucun code de la SUT elle-même.

En réponse à la mise à jour de l'OP à ma réponse :

Oui, sur la base de mes connaissances, mais une expérience quelque peu limitée dans la TDD, je choisirais l'option n ° 3.

28
DXM

Et si le code était comme suit:

MyTarget() // constructor
{
   Field1 = Field2 = Field3 = Field4 = "";
}

Votre deuxième exemple n'aurait pas attrapé le bogue, mais le premier exemple.

En général, je recommanderais contre le codage logiciel car il peut cacher des bugs. Par exemple:

string expected = "C:\\Output Folder" + string.Join("\\", target.Field1, target.Field2, target.Field3, target.Field4);

Pouvez-vous repérer le problème? Vous ne feriez pas la même erreur dans une version codée dur. Il est plus difficile d'obtenir les calculs corrects que les valeurs codées dures. C'est pourquoi je préfère travailler avec des valeurs codées rigides que celles-ci.

Mais il y a des exceptions. Et si votre code doit exécuter Windows et Linux? Non seulement le chemin doit être différent, il doit utiliser différents séparateurs de chemins! Calculer le chemin à l'aide de fonctions qui résoutit la différence entre peut avoir un sens dans ce contexte.

16
Winston Ewert

À mon avis, vos deux suggestions sont moins qu'idéal. Le moyen idéal de le faire est celui-ci:

[TestMethod]
public void GetPath_Hardcoded()
{
    const string f1 = "fields"; const string f2 = "that later"; 
    const string f3 = "determine"; const string f4 = "a folder";

    MyClass target = new MyClass( f1, f2, f3, f4 );
    string expected = "C:\\Output Folder\\" + string.Join("\\", f1, f2, f3, f4);
    string actual = target.GetPath();
    Assert.AreEqual(expected, actual,
        "GetPath should return a full directory path based on its fields.");
}

En d'autres termes, le test doit fonctionner exclusivement sur la base de l'entrée et de la sortie de l'objet, et non basé sur l'état interne de l'objet. L'objet doit être traité comme une boîte noire. (Je ne regarde pas d'autres problèmes, comme l'inadéquation de l'utilisation de String.Join au lieu de path.combine, car ceci n'est qu'un exemple.)

4
Mike Nakis

Il y a beaucoup de concepts possibles, fait quelques exemples pour voir la différence

[TestMethod]
public void GetPath_Softcoded()
{
    //Hardcoded since you want to see what you expect is most simple and clear
    string expected = "C:\\Output Folder\\fields\\that later\\determine\\a folder";

    //If this test should also use a mocked filesystem it might be that you want to use
    //some base directory, which you could set in the setUp of your test class
    //that is usefull if you you need to run the same test on different environments
    string expected = this.outputPath + "fields\\that later\\determine\\a folder";


    //another readable way could be interesting if you have difficult variables needed to test
    string fields = "fields";
    string thatLater = "that later";
    string determine = "determine";
    string aFolder = "a folder";
    string expected = this.outputPath + fields + "\\" + thatLater + "\\" + determine + "\\" + aFolder;
    MyClass target = new MyClass(fields, thatLater, determine, aFolder);

    //in general testing with real words is not needed, so code could be shorter on that
    //for testing difficult folder names you write a separate test anyway
    string f1 = "f1";
    string f2 = "f2";
    string f3 = "f3";
    string f4 = "f4";
    string expected = this.outputPath + f1 + "\\" + f2 + "\\" + f3 + "\\" + f4;
    MyClass target = new MyClass(f1, f2, f3, f4);

    //so here we start to see a structure, it looks more like an array of fields
    //so what would make testing more interesting with lots of variables is the use of a data provider
    //the data provider will re-use your test with many different kinds of inputs. That will reduce the amount of duplication of code for testing
    //http://msdn.Microsoft.com/en-us/library/ms182527.aspx


    The part where you compare already seems correct
    MyClass target = new MyClass(fields, thatLater, determine, aFolder);

    string actual = target.GetPath();
    Assert.AreEqual(expected, actual,
        "GetPath should return a full directory path based on its fields.");
}

Pour résumer: En général, votre premier essai codé en dur fait le plus de sens pour moi, car il est simple, tout droit au point, etc. Si vous commencez à obtenir un chemin d'accès à un chemin trop élevé, mettez-le dans la méthode de configuration.

Pour plus de futurs tests structurés, je voudrais vérifier les données de données afin que vous puissiez simplement ajouter plus de lignes de données si vous avez besoin de plus de situations de test.

0
Luc Franken

Les cadres de test modernes vous permettent de fournir des paramètres à votre méthode. J'utiliserais ceux:

[TestCase("fields", "that later", "determine", "a folder", @"C:\Output Folder\fields\that later\determine\a folder")]
public void GetPathShouldReturnFullDirectoryPathBasedOnItsFields(
    string field1, string field2, string field3, string field,
    string expected)
{
    MyClass target = new MyClass(field1, field2, field3, field4);
    string actual = target.GetPath();
    Assert.AreEqual(expected, actual,
        "GetPath should return a full directory path based on its fields.");
}

Cela présente plusieurs avantages, à mon avis:

  1. Les développeurs sont souvent tentés de copier les parties apparemment simples du code de leur SUT dans leurs tests unitaires. Comme Winston souligne , celles-ci peuvent toujours avoir des bugs délicats cachés dans eux. "Codage dur" Le résultat attendu contribue à éviter les situations où votre code de test est incorrect pour la même raison que votre code d'origine est incorrect. Mais si une modification des exigences vous oblige à retrouver les cordes codées dures intégrées à l'intérieur des dizaines de méthodes de test, cela peut être ennuyeux. Avoir toutes les valeurs codées dures au même endroit, en dehors de votre logique de test, vous donne le meilleur des deux mondes.
  2. Vous pouvez ajouter des tests pour différentes entrées et sorties attendues avec une seule ligne de code. Cela vous encourage à écrire plus de tests, tout en conservant votre code de test DRY et facile à entretenir. Je trouve cela parce que c'est tellement bon marché d'ajouter des tests, mon esprit est ouvert à de nouveaux cas de test que je ne voudrais pas Ça a pensé si j'avais dû écrire une nouvelle méthode pour eux. Par exemple, quel comportement serais-je attendu si l'une des contributions l'ait ponctué? Une barre oblique inverse? Et si on était vide? Ou espace? Ou commencé? Ou commencé ou fini par des espaces blanche?
  3. Le cadre de test traitera chaque témoignage comme son propre test, même en mettant les entrées et les sorties fournies dans le nom de test. Si toutes les tests passent, mais un, il est super facile de voir lequel on s'est cassé et comment c'était différent de tous les autres.
0
StriplingWarrior