L'utilisation de injection de dépendance (DI) est-elle essentielle pour les tests unitaires?
Je ne peux pas penser à une autre alternative pour isoler le code afin qu'il puisse être testé. De plus, tous les exemples que j'ai jamais vus utilisent ce modèle. Est-ce parce que c'est la seule option viable ou existe-t-il d'autres alternatives?
DI facilite les tests unitaires. Mais vous pouvez toujours écrire des tests unitaires sans DI. De nombreux tests unitaires ont déjà été écrits avant que l'ID ne se généralise. (Bien sûr, certaines de ces techniques utilisées identiques ou très similaires à DI sans savoir qu'elle a un nom fantaisie :-)
J'ai moi-même utilisé par exemple interfaces et usines beaucoup avant de se renseigner sur DI. Le nom de la classe d'usine réelle peut avoir été lu dans un fichier de configuration ou transmis au SUT comme argument.
Une autre approche consiste à utiliser des singletons (ou des données accessibles à l'échelle mondiale en général). Oui, je sais que ce n'est pas recommandé par beaucoup (y compris moi-même) en général. Néanmoins, peut être viable dans des situations spécifiques, en particulier si le singleton contient des données de configuration statiques qui ne sont pas spécifiques au cas de test, mais diffèrent entre la production et l'environnement de test. Bien sûr, il a ses problèmes connus, donc DI est supérieur si vous pouvez l'utiliser. Mais souvent (par exemple dans les anciens systèmes), vous ne pouvez pas.
En parlant de cela, Travailler efficacement avec le code hérité décrit de nombreuses astuces pour obtenir le code hérité couvert par des tests. Beaucoup d'entre eux ne sont pas Nice et ne sont pas conçus comme une solution à long terme. Mais ils vous permettent de créer les premiers tests unitaires précieux sur un système par ailleurs non testable ... ce qui vous permet de commencer la refactorisation, et éventuellement (entre autres) d'introduire DI.
Découplage est essentiel pour les tests unitaires. DI est un excellent moyen de réaliser le découplage.
Selon les technologies que vous utilisez, vous pouvez isoler les dépendances sans utiliser DI. Par exemple, dans le monde .NET, Moles vous permet d'isoler les dépendances sans le modèle DI.
Cela dit, je crois que ces cadres d'isolement sont écrits et destinés à des situations dans votre code avec des dépendances externes (système de fichiers, base de données, etc.). Autrement dit, le fait que l'on puisse faire cela ne signifie pas qu'il ou elle devrait.
L'injection de dépendances permet des tests unitaires, mais elle permet également de modifier le comportement d'un objet sans altérer le code de cet objet (principe ouvert/fermé). Donc, ce n'est pas seulement du code testable, mais du code flexible qui en résulte. J'ai généralement constaté qu'il existe une forte corrélation entre le code maintenable/flexible et le code testable.
Non, l'ID n'est pas indispensable pour les tests unitaires, mais cela aide beaucoup.
Vous pouvez utiliser sines ou localisateurs et tester comme vous le feriez avec DI (tout simplement pas aussi élégant et nécessiterait plus de configuration).
En outre, Mock Objects serait important dans les systèmes hérités où de nombreux appels sont délégués à des fonctions plutôt qu'à des dépendances. (Les objets fantaisie peuvent également être largement utilisés dans une configuration appropriée)
Il peut y avoir des configurations où les tests sont presque impossibles. Mais cela ne dépend pas de l'utilisation ou non de l'injection de dépendance.
Non, l'injection de dépendances n'est pas essentielle pour les tests unitaires.
L'injection de dépendances est utile si vous avez une classe qui a besoin d'une instance de classe dépendante pour effectuer un sous-traitement. Au lieu de DI, vous pouvez séparer la logique d'une méthode métier en une partie de collecte de données (qui n'est pas testable par unité) et une partie de calcul qui peut être testée par unité.
Exemple (en utilisant DI) Cette implémentation dépend de l'employé, du compte, ...
bool hasPermissionToTransferMoney(Employee employee, Account from, Account to, Money amount)
{
if (amount > 100 && employee.isStudent())
return false;
if (to.getOwner().getFamiliyName() == employee.getFamilyName() && ...
return false; // cannot transfer money to himself;
...
}
Après séparation de la collecte et du calcul des données:
bool hasPermissionToTransferMoney(Employee employee, Account from, Account to, Money amount)
{
return hasPermissionToTransferMoney(employee.isStudent(), employee.getFamilyName(), to.getOwner().getFamilyName(), ...);
}
// the actual permission calculation
static bool hasPermissionToTransferMoney(boolean isStudent, string employeeFamilyName, string receiverFamilyName, ...)
if (amount > 100 && isStudent)
return false;
if (receiverFamilyName == employeeFamiliyName && ...
return false; // cannot transfer money to himself
...
}
La partie calcul peut être facilement testée sans injection de dépendance.
L'injection de dépendance n'est pas essentielle pour les tests unitaires
En revanche, l'inversion du contrôle est essentielle lorsque vous souhaitez échanger une implémentation pour une autre.
Oui, il existe des alternatives à l'utilisation de DI pour l'isolement.
Une alternative consiste à utiliser des usines ou un ServiceLocator qui peut être configuré à partir de tests pour renvoyer des objets factices au lieu de vrais.
Une autre consiste à utiliser un cadre d'isolation ou un outil de simulation approprié. De tels outils existent pour à peu près tous les langages de programmation modernes (pour Java, C #, Ruby et Python, au moins). Ils peuvent isoler une classe en cours de test de l'implémentation d'autres classes/types, même lorsque la classe testée instancie directement ses dépendances.