Dernièrement, j'ai beaucoup réfléchi à la meilleure façon de "simuler" une méthode statique appelée à partir d'une classe que j'essaie de tester. Prenons l'exemple du code suivant :
using (FileStream fStream = File.Create(@"C:\test.txt"))
{
string text = MyUtilities.GetFormattedText("hello world");
MyUtilities.WriteTextToFile(text, fStream);
}
Je comprends qu'il s'agit d'un mauvais exemple, mais il comporte trois appels de méthodes statiques qui sont toutes légèrement différentes. La fonction File.Create accède au système de fichiers et je ne possède pas cette fonction. La fonction MyUtilities.GetFormattedText est une fonction qui m'appartient et qui est purement statique. Enfin, la fonction MyUtilities.WriteTextToFile est une fonction qui m'appartient et qui accède au système de fichiers.
Ce à quoi j'ai réfléchi dernièrement, c'est que s'il s'agissait d'un code hérité, comment pourrais-je le remanier pour le rendre plus testable à l'unité. J'ai entendu plusieurs arguments selon lesquels les fonctions statiques ne devraient pas être utilisées parce qu'elles sont difficiles à tester. Je ne suis pas d'accord avec cette idée parce que les fonctions statiques sont utiles et je ne pense pas qu'un outil utile doive être rejeté simplement parce que le cadre de test utilisé ne peut pas le gérer très bien.
Après de longues recherches et délibérations, je suis parvenu à la conclusion qu'il y a essentiellement 4 modèles ou pratiques qui peut être utilisée pour rendre les fonctions qui appellent des fonctions statiques testables à l'unité. Il s'agit notamment des éléments suivants :
- Ne vous moquez pas la fonction statique et laisser le test unitaire l'appeler.
- Enveloppez la méthode statique dans une classe d'instance qui implémente une interface avec la fonction dont vous avez besoin, puis utilisez l'injection de dépendance pour l'utiliser dans votre classe. C'est ce que j'appellerai injection de dépendance d'interface .
- Utilisation Taupes (ou TypeMock) pour détourner l'appel de fonction.
- Utiliser l'injection de dépendance pour la fonction. Je l'appellerai l'injection de dépendances de fonctions .
J'ai beaucoup entendu parler des trois premières pratiques, mais alors que je réfléchissais à des solutions à ce problème, la quatrième idée m'est venue, à savoir l'injection de dépendances de fonctions . Cela revient à cacher une fonction statique derrière une interface, mais sans qu'il soit nécessaire de créer une interface et une classe enveloppante. Voici un exemple de cette méthode :
public class MyInstanceClass
{
private Action<string, FileStream> writeFunction = delegate { };
public MyInstanceClass(Action<string, FileStream> functionDependency)
{
writeFunction = functionDependency;
}
public void DoSomething2()
{
using (FileStream fStream = File.Create(@"C:\test.txt"))
{
string text = MyUtilities.GetFormattedText("hello world");
writeFunction(text, fStream);
}
}
}
Parfois, la création d'une interface et d'une classe enveloppante pour l'appel d'une fonction statique peut s'avérer fastidieuse et polluer votre solution avec un grand nombre de petites classes dont le seul but est d'appeler une fonction statique. Je suis tout à fait favorable à l'écriture d'un code facilement testable, mais cette pratique semble être une solution de contournement pour un mauvais cadre de test.
En réfléchissant à ces différentes solutions, j'ai compris que les quatre pratiques mentionnées ci-dessus peuvent être appliquées dans différentes situations. Voici ce que je pense être la les circonstances correctes pour appliquer les pratiques susmentionnées :
- Ne vous moquez pas la fonction statique si elle est purement sans état et n'accède pas aux ressources du système (telles que le système de fichiers ou une base de données). Bien entendu, on peut faire valoir que si l'on accède aux ressources du système, cela introduit de toute façon de l'état dans la fonction statique.
- Utilisation l'injection de dépendance d'interface lorsque vous utilisez plusieurs fonctions statiques qui peuvent logiquement être ajoutées à une interface unique. La clé ici est qu'il y a plusieurs fonctions statiques utilisées. Je pense que dans la plupart des cas, ce n'est pas le cas. Il n'y aura probablement qu'une ou deux fonctions statiques appelées dans une fonction.
- Utilisation Taupes lorsque vous mockez des bibliothèques externes telles que des bibliothèques d'interface utilisateur ou des bibliothèques de base de données (telles que linq to sql). Mon opinion est que si Moles (ou TypeMock) est utilisé pour détourner le CLR afin de simuler votre propre code, alors c'est un indicateur qu'un refactoring doit être fait pour découpler les objets.
- Utilisation l'injection de dépendances de fonctions lorsqu'il y a un petit nombre d'appels de fonctions statiques dans le code testé. C'est le modèle vers lequel je me tourne dans la plupart des cas pour tester les fonctions qui appellent des fonctions statiques dans mes propres classes utilitaires.
Ce sont là mes réflexions, mais j'apprécierais vraiment d'avoir un retour d'information à ce sujet. Quelle est la meilleure façon de tester un code dans lequel une fonction statique externe est appelée ?