J'ai une fonction qui retourne la disponibilité d'un module, où le module n'est disponible que si plusieurs conditions sont toutes remplies. Le code ressemble à ceci:
bool isShipAvailable() {
bool isAvailable;
isAvailable = this->areSystemsCalibrated;
isAvailable = isAvailable && this->areEnginesFunctional;
isAvailable = isAvailable && this->isWarpDriveOnline;
isAvailable = isAvailable && this->isFluxCapacitorOnline;
isAvailable = isAvailable && this->areStabilizersOnline;
return isAvailable;
}
J'essaie de tester ce code sans créer quelque chose de grand ou difficile à maintenir. Je sais que je pourrais écrire des tests pour toutes les permutations, mais il serait acceptable de tester le cas où toutes les conditions sont true
, puis testez chaque scénario où une seule condition est false
? Cela ferait-il trop d'hypothèses sur la mise en œuvre dans les tests?
EDIT: Je ne demande pas comment refactoriser la logique booléenne pour être concise, mais plutôt ce que le moyen le plus court de tester chaque permutation de cette logique serait. Les détails de la mise en œuvre elle-même ne sont pas terriblement importants.
Le test de l'unité concerne le comportement du code dans le test du point de vue de ses clients. Par conséquent, comme votre classe de test est l'un de ses clients, vous devez définir quelles sont vos attentes.
Il y a quelques attentes que je vois de votre mise en œuvre:
Par conséquent, j'attendrais 6 tests, car il y a 6 comportements distincts que je me soucie d'un client.
Cela ferait-il trop d'hypothèses sur la mise en œuvre dans les tests?
Bien drôle, parce que je crois en fait que l'inverse est vrai: vous faites trop d'hypothèses sur votre implémentation lorsque vous testez chaque combinaison. N'oubliez pas que vous testez toujours du point de vue de vos clients. Si vous testez chaque combinaison, vous dites que vos clients se soucient de toutes ces combinaisons. Ma compréhension de votre code est que peu importe la combinaison de systèmes hors ligne ou non fonctionnelle. Tant que l'un sous-système est hors ligne, le navire n'est pas disponible, période. Par conséquent, je le testerais comme tel.
1. Ouf, cela pourrait nécessiter une configuration assez d'une configuration pour tester cette affirmation. Si c'est votre sentiment aussi, alors vos tests vous parlent-ils: votre code sous test fait beaucoup de choses.
La bibliothèque standard fournit une fonction spécifiquement et explicitement pour des tâches telles que celles-ci. Je l'utiliserais simplement et je vois peu de raisons de tester que cela fait du travail.
#include <algorithm>
class foo {
bool areSystemsCalibrated,
areEnginesFunctional,
isWarpDriveOnline,
isFluxCapcacitorOnline,
areStabilizersOnline;
bool foo::*conditions[5] = {
&foo::areSystemsCalibrated,
&foo::areEnginesFunctional,
&foo::isWarpDriveOnline,
&foo::isFluxCapcacitorOnline,
&foo::areStabilizersOnline
};
public:
bool isShipAvailable() const {
return std::all_of(std::begin(conditions), std::end(conditions),
[&](bool foo::*b) { return this->*b; });
}
};
Si, à ce stade, vous voulez vraiment le tester de toute façon, vous avez une collection de conditions. Cela rend relativement simple à tester toute combinaison de conditions que vous voyez et y compris un test exhaustif de chaque combinaison possible, si vous voulez vraiment.
avec un nombre important, mais fini de cas de test test piloté par les données est la réponse.
... Test effectué à l'aide d'une table de conditions directement sous forme d'entrées de test et de sorties vérifiables, ainsi que du processus où les paramètres d'environnement de test et le contrôle ne sont pas codés en dur. Dans la forme la plus simple, le testeur fournit les entrées d'une rangée dans la table et attend les sorties qui se produisent dans la même ligne. La table contient typiquement des valeurs correspondant aux espaces d'entrée de la limite ou de la partition.
Vous avez besoin d'un test explicite dans lequel toutes les conditions sont vraies, disant "ça marche".
En utilisant un test axé sur les données, vous pouvez tester toutes les valeurs "False" en paramétrant chaque champ booléen comme entrée au test.
Comment cela dépendez-vous de la pile technique et du cadre de test unitaire, mais il pourrait être réduit à une méthode de test et une boucle à boucle qui itère sur les entrées attendues (exemple dans C #, MS Test Framework):
[TestClass]
public class ShipTests
{
[TestMethod]
public void ItWorks()
{
var ship = new SpaceShip(true, true, true, true, true);
Assert.IsTrue(ship.isShipAvailable());
}
[TestMethod]
public void ItDoesntWork()
{
var inputs = new bool[][]
{
{ false, true, true, true, true },
{ false, false, true, true, true },
// ...
{ false, false, false, false, false },
};
foreach (var row in inputs)
{
var ship = new SpaceShip(row[0], row[1], row[2], row[3], row[4]);
Assert.IsFalse(ship.isShipAvailable());
}
}
}
Robert Harvey a commenté la question:
Cela dit, si vous voulez vraiment des tests pour cela, je pense qu'un test avec tous les drapeaux true et un test avec l'un des drapeaux faux devait vraiment suffire.
Cela ne suffit pas, car tout drapeau étant faux causant le navire indisponible. Vous ne voulez pas tester que le lecteur de chaîne n'est pas disponible et indiquez que tous les tests passent si un défaut provoque le condensateur de flux d'être hors ligne de manière inattendue.
Le condensateur Flux est important, vous savez. Très important. Il doit être en ligne quoi que ce soit - encore plus que Facebook!
Sinon, il y a 5 drapeaux ici; Un test totalement complet nécessiterait 32 permutations de test.
Oui, il existe un grand nombre de cas de test, mais le travail nécessaire pour maintenir ces tests est minimal si l'utilisation d'un test axé sur les données. Et s'il s'agit de toutes les manipulations de données en mémoire, le temps d'exécution du test est négligeable. Je pense que j'ai passé plus de temps à écrire cette réponse que tous les développeurs de l'équipe consacreront à exécuter ces tests au cours de la vie du projet.