La statique est-elle universellement «perverse» pour les tests unitaires et si oui, pourquoi Resharper le recommande-t-il? [fermé]

85

J'ai constaté qu'il n'y a que 3 façons de dépendre d'un test unitaire (fictif / stub) statique dans C # .NET:

Étant donné que deux d’entre eux ne sont pas gratuits et que l’un n’a pas encore atteint la version 1.0, se moquer de choses statiques n’est pas si facile.

Est-ce que cela rend les méthodes statiques et un tel "mal" (dans le sens de tests unitaires)? Et si oui, pourquoi le resharper veut-il que je fasse tout ce qui peut être statique, statique? (En supposant que resharper n'est pas aussi "mauvais".)

Précision: Je parle du scénario quand vous voulez tester l' unité une méthode et cette méthode appelle une méthode statique dans une autre unité / classe. Selon la plupart des définitions de test unitaire, si vous laissez simplement la méthode testée appeler la méthode statique dans l’autre unité / classe, vous ne testez pas l’ unité, mais bien le test d’ intégration . (Utile, mais pas un test unitaire.)

Vaccano
la source
3
TheLQ: Vous pouvez. Je pense qu'il parle de ne pas être capable de tester des méthodes statiques car cela concerne souvent des variables statiques. Ainsi, changer l'état après et entre test.
26
Personnellement, je pense que vous allez trop loin dans la définition de "unité". "Unité" doit être "la plus petite unité qu'il est logique de tester isolément". Cela peut être une méthode, peut-être plus que cela. Si la méthode statique n'a pas d'état et est bien testée, alors avoir un deuxième appel de test unitaire (IMO) n'est pas un problème.
mlk
10
"Personnellement, je pense que vous prenez la définition de" unité "trop ​​loin." Non, c'est juste qu'il utilise une utilisation standard et que vous créez votre propre définition.
7
"pourquoi le resharper veut-il que je fasse tout ce qui peut être statique, statique?" Resharper ne veut rien faire de votre part. Il s'agit simplement de vous informer que la modification est possible et peut - être souhaitable à partir d'un POV d'analyse de code. Resharper ne remplace pas votre propre jugement!
Adam Naylor
4
@ acidzombie24. Les méthodes classiques peuvent également modifier l'état statique, elles sont donc aussi "mauvaises" que les méthodes statiques. Le fait qu'ils puissent également modifier l'état avec un cycle de vie plus court les rend encore plus dangereux. (Je ne suis pas en faveur des méthodes statiques, mais le problème de la modification d'état est un coup porté aux méthodes régulières aussi, encore plus)
mike30

Réponses:

105

En regardant les autres réponses ici, je pense qu'il pourrait y avoir une certaine confusion entre les méthodes statiques qui maintiennent un état statique ou provoquent des effets secondaires (ce qui me semble une très mauvaise idée) et les méthodes statiques qui renvoient simplement une valeur.

Les méthodes statiques qui ne tiennent aucun état et ne provoquent aucun effet secondaire devraient être facilement testables à l'unité. En fait, je considère de telles méthodes comme une forme de programmation fonctionnelle du "pauvre homme"; vous remettez à la méthode un objet ou une valeur, qui renvoie un objet ou une valeur. Rien de plus. Je ne vois pas en quoi de telles méthodes auraient un impact négatif sur les tests unitaires.

Robert Harvey
la source
44
La méthode statique est unitaire testable, mais qu'en est-il des méthodes qui appellent la méthode statique? Si les appelants appartiennent à une autre classe, ils ont une dépendance à découpler pour effectuer un test unitaire.
Vaccano
22
@ Vaccano: Mais si vous écrivez des méthodes statiques qui ne conservent ni leur statut ni leurs effets secondaires, elles sont de toute façon plus ou moins équivalentes sur le plan fonctionnel à un stub.
Robert Harvey
20
Les plus simples peut-être. Mais les plus complexes peuvent commencer à générer des exceptions et avoir des sorties inattendues (et devraient être comprises dans le test unitaire pour la méthode statique, pas dans le test unitaire pour l'appelant de la méthode statique), ou du moins c'est ce que je ont été amenés à croire à la littérature que j'ai lue.
Vaccano
21
@Vaccano: une méthode statique avec des sorties ou des exceptions aléatoires a des effets secondaires.
Tikhon Jelvis
10
@TikhonJelvis Robert a parlé de sorties; et les exceptions "aléatoires" ne doivent pas être un effet secondaire, elles sont essentiellement une forme de sortie. Le fait est que chaque fois que vous testez une méthode qui appelle la méthode statique, vous enroulez cette méthode et toutes les permutations de sa sortie potentielle, et vous ne pouvez pas tester votre méthode séparément.
Nicole
26

Vous semblez confondre les données statiques et les méthodes statiques . Si je me souviens bien, Resharper recommande de rendre les privateméthodes d'une classe statiques si elles peuvent l'être - je pense que cela produit un petit avantage en termes de performances. Il ne recommande pas de faire "tout ce qui peut être" statique!

Il n’ya rien de mal avec les méthodes statiques et elles sont faciles à tester (tant qu’elles ne modifient pas les données statiques). Par exemple, pensez à une bibliothèque Maths, qui est un bon candidat pour une classe statique avec des méthodes statiques. Si vous avez une méthode (artificielle) comme celle-ci:

public static long Square(int x)
{
    return x * x;
}

alors cela est éminemment testable et n’a pas d’effets secondaires. Vous vérifiez que lorsque vous passez, par exemple, vous en récupérez 400. Pas de problème.

Dan Diplo
la source
4
Que se passe-t-il lorsque vous avez une autre classe qui appelle cette méthode statique? Cela semble simple dans ce cas, mais il s'agit d'une dépendance qui ne peut être isolée qu'avec l'un des trois outils répertoriés ci-dessus. Si vous ne l'isolez pas, vous n'êtes pas "testeur d'unité", mais "test d'intégration" (car vous testez la
qualité d'
3
Rien ne se passe. Pourquoi le ferait-il? Le framework .NET est plein de méthodes statiques. Voulez-vous dire que vous ne pouvez pas non plus utiliser ces méthodes dans les méthodes que vous souhaitez tester?
Dan Diplo
3
Eh bien, si votre code est au niveau production / qualité du .NET Framework, alors allez-y. Mais le point de départ des tests unitaires est le test de l'unité, pris isolément. Si vous appelez également des méthodes dans d'autres unités (qu'elles soient statiques ou autres), vous testez maintenant votre unité ainsi que ses dépendances. Je ne dis pas que ce n'est pas un test utile, mais pas un "test unitaire" selon la plupart des définitions. (Parce que vous testez maintenant votre unité en cours de test et l'unité qui utilise la méthode statique).
Vaccano
10
Vraisemblablement, vous (ou d’autres) avez déjà testé la méthode statique et montré qu’elle fonctionnait (au moins comme prévu) avant d’écrire trop de code autour de celle-ci. Si quelque chose se brise dans la partie que vous testez ensuite, c’est là que vous devriez regarder en premier, et non dans les éléments que vous avez déjà testés.
cHao
6
@Vaccano Alors, comment Microsoft teste-t-il le .NET Framework, hein? De nombreuses classes du Framework System.Mathutilisent des méthodes statiques dans d'autres classes (telles que ), sans parler de l'abondance des méthodes d'usine statique, etc. De plus, vous ne pourrez jamais utiliser de méthodes d'extension, etc. fondamental aux langues modernes. Vous pouvez les tester isolément (car ils seront généralement déterministes), puis les utiliser dans vos classes sans souci. Ce n'est pas un problème!
Dan Diplo
18

Si la vraie question est "Comment puis-je tester ce code?":

public class MyClass
{
   public void MethodToTest()
   {
       //... do something
       MyStaticClass.StaticMethod();
       //...more
   }
}

Ensuite, il suffit de refactoriser le code et d’injecter comme d’habitude l’appel à la classe statique comme ceci:

public class MyClass
{
   private readonly IExecutor _externalExecutor;
   public MyClass(_IExecutor executor)
   {
       _exeternalExecutor = executor;
   }

   public void MethodToTest()
   {
       //... do something
       _exetrnalExecutor.DoWork();
       //...more
   }
}

public class MyStaticClassExecutor : IExecutor
{
    public void DoWork()
    {
        MyStaticClass.StaticMethod();
    }
}
Ensoleillé
la source
9
Heureusement, nous avons également des questions concernant la lisibilité du code et KISS sur SO également :)
gbjbaanb
un délégué ne serait-il pas plus facile et ferait-il la même chose?
jk.
@jk pourrait être, mais il est difficile d'utiliser le conteneur IoC alors.
Ensoleillé
15

Les statiques ne sont pas nécessairement diaboliques, mais elles peuvent limiter vos options en matière de tests unitaires avec des faux / mocs / moignons.

Il existe deux approches générales pour se moquer.

Le premier (traditionnel - mis en œuvre par RhinoMocks, Moq, NMock2; des simulacres et des bouts manuels se trouvent également dans ce camp) repose sur des coutures de test et une injection de dépendance. Supposons que vous testiez du code statique avec des dépendances. Ce qui se produit souvent dans le code conçu de cette manière, c’est que les statiques créent leurs propres dépendances, en inversant l’ inversion des dépendances . Vous découvrez bientôt que vous ne pouvez pas injecter d'interfaces simulées dans un code à tester conçu de cette manière.

La seconde (rien à faire - implémentée par TypeMock, JustMock et Moles) repose sur l’ API de profilage de .NET . Il peut intercepter n'importe laquelle de vos instructions CIL et remplacer une partie de votre code par un faux. Cela permet à TypeMock et aux autres produits de ce camp de se moquer de n'importe quoi: statique, classes scellées, méthodes privées - des éléments non conçus pour être testés.

Il y a un débat en cours entre deux écoles de pensée. On dit, suivez les principes et la conception de SOLID pour la testabilité (cela implique souvent de passer facilement à la statique). L'autre dit, achetez TypeMock et ne vous inquiétez pas.

Azheglov
la source
14

Cochez cette case: "Les méthodes statiques sont mortelles pour la testabilité" . Bref résumé de l'argumentation:

Pour tester les unités, vous devez prendre un petit morceau de votre code, recâbler ses dépendances et le tester isolément. Cela est difficile avec les méthodes statiques, non seulement dans le cas où elles accèdent à l'état global, mais même si elles n'appellent que d'autres méthodes statiques.

Rafał Dowgird
la source
32
Je ne conserve pas l'état global ni ne cause d'effets secondaires dans mes méthodes statiques. Cet argument ne me semble donc pas pertinent. L'article que vous avez lié constitue un argument de pente glissante qui n'a aucune base si les méthodes statiques sont limitées à un code procédural simple, agissant de manière "générique" (comme les fonctions mathématiques).
Robert Harvey
4
@ Robert Harvey - comment testez-vous une méthode qui utilise une méthode statique d'une autre classe (c'est-à-dire qu'elle dépend d'une méthode statique)? Si vous le laissez appeler, vous n'êtes pas "test unitaire" mais "test d'intégration"
Vaccano
10
Ne vous contentez pas de lire l’article du blog, lisez les nombreux commentaires qui le contredisent. Un blog est juste un avis, pas un fait.
Dan Diplo
8
@Vaccano: une méthode statique sans effet secondaire ni état externe est fonctionnellement équivalente à une valeur. Sachant cela, ce n'est pas différent du test unitaire d'une méthode qui crée un entier. C’est l’une des idées clés de la programmation fonctionnelle.
Steven Evers
3
Si les systèmes intégrés ne fonctionnent pas ou si une application dont les bogues risquent de créer des incidents internationaux, IMO, lorsque la "testabilité" pilotait l'architecture, vous l'aviez tordue avant d'avoir adopté des méthodologies de test à chaque dernier virage. De plus, je suis fatigué de la version XP de tout ce qui domine chaque conversation. XP n'est pas une autorité, c'est une industrie. Voici la définition originale et judicieuse du test unitaire: python.net/crew/tbryan/UnitTestTalk/slide2.html
Erik Reppen
5

La simple vérité rarement reconnue est que si une classe contient une dépendance visible du compilateur sur une autre classe, elle ne peut pas être testée indépendamment de cette classe. Vous pouvez simuler quelque chose qui ressemble à un test et qui apparaîtra dans un rapport comme s'il s'agissait d'un test.

Mais il n'aura pas les propriétés clés définissant un test; échouer quand les choses vont mal, passer quand ils ont raison.

Ceci s'applique à tous les appels statiques, aux appels de constructeur et à toute référence à des méthodes ou à des champs non hérités d'une classe ou d'une interface de base . Si le nom de la classe apparaît dans le code, il s'agit d'une dépendance visible du compilateur, et vous ne pouvez pas valider sans elle. Tout petit morceau n'est tout simplement pas une unité testable valide . Toute tentative visant à le traiter comme tel ne produira pas de résultats plus significatifs que l'écriture d'un petit utilitaire permettant d'émettre le code XML utilisé par votre infrastructure de test pour indiquer 'test réussi'

Compte tenu de cela, il existe trois options:

  1. définissez les tests unitaires comme des tests de l'unité composée d'une classe et de ses dépendances codées en dur. Cela fonctionne, à condition d'éviter les dépendances circulaires.

  2. ne créez jamais de dépendances à la compilation entre les classes que vous êtes chargé de tester. Cela fonctionne, à condition que le style de code obtenu ne vous dérange pas.

  3. ne faites pas de test unitaire, mais un test d'intégration. Ce qui fonctionne, à condition que cela n'entre pas en conflit avec quelque chose d'autre pour lequel vous devez utiliser le terme tests d'intégration.

soru
la source
3
Rien ne dit qu'un test unitaire est un test d'une seule classe. C'est un test d'une seule unité. Les propriétés qui définissent les tests unitaires sont leur rapidité, leur répétabilité et leur indépendance. Référencer Math.Pidans une méthode n'en fait pas un test d'intégration selon une définition raisonnable.
Sara
C'est-à-dire l'option 1. Je conviens que c'est la meilleure approche, mais il peut être utile de savoir que d'autres personnes utilisent les termes différemment (de manière raisonnable ou non).
soru
4

Il n'y a pas deux manières à ce sujet. Les suggestions de ReSharper et plusieurs fonctionnalités utiles de C # ne seraient pas utilisées aussi souvent si vous écriviez des tests d'unité atomique isolés pour tout votre code.

Par exemple, si vous avez une méthode statique et que vous devez l'exclure, vous ne pouvez pas le faire sauf si vous utilisez un cadre d'isolation basé sur un profil. Une solution de contournement compatible avec les appels consiste à modifier le haut de la méthode pour utiliser la notation lambda. Par exemple:

AVANT:

    public static DBConnection ConnectToDB( string dbName, string connectionInfo ) {
    }

APRÈS:

    public static Func<string, string, DBConnection> ConnectToDB (dbName, connectionInfo ) {
    };

Les deux sont compatibles avec les appels. Les appelants n'ont pas à changer. Le corps de la fonction reste le même.

Ensuite, dans votre code de test unitaire, vous pouvez stub cet appel comme suit (en supposant qu'il soit dans une classe appelée Database):

        Database.ConnectToDB = (dbName, connectionInfo) => { return null|whatever; }

Veillez à le remplacer par la valeur d'origine une fois que vous avez terminé. Vous pouvez le faire via try / finally ou, lors du nettoyage d'un test un, celui qui est appelé après chaque test, écrivez un code tel que celui-ci:

    [TestCleanup]
    public void Cleanup()
    {
        typeof(Database).TypeInitializer.Invoke(null, null);
    }

qui ré-invoquera l'initialiseur statique de votre classe.

Les fonctions lambda ne sont pas aussi riches en support que les méthodes statiques classiques, cette approche a donc les effets secondaires indésirables suivants:

  1. Si la méthode statique était une méthode d'extension, vous devez d'abord la remplacer par une méthode autre que d'extension. Resharper peut le faire pour vous automatiquement.
  2. Si l'un des types de données des méthodes statiques est un assemblage imbriqué-interopéré, comme pour Office, vous devez envelopper la méthode, envelopper le type ou le changer en type 'objet'.
  3. Vous ne pouvez plus utiliser l'outil de refactoring de changement de signature de Resharper.

Mais supposons que vous évitiez complètement la statique et que vous convertissiez cela en une méthode d'instance. Ce n'est toujours pas mockable sauf si la méthode est virtuelle ou implémentée dans le cadre d'une interface.

Donc, en réalité, quiconque suggère que le remède aux méthodes statiques réductrices consiste à en faire des méthodes d'instance, ils seraient également opposés aux méthodes d'instance qui ne sont pas virtuelles ou ne font pas partie d'une interface.

Alors pourquoi C # a-t-il des méthodes statiques? Pourquoi autorise-t-il les méthodes d'instance non virtuelle?

Si vous utilisez l'une de ces "fonctionnalités", vous ne pouvez tout simplement pas créer de méthodes isolées.

Alors quand les utilisez-vous?

Utilisez-les pour tout code dont vous ne vous attendez pas à ce que quelqu'un veuille se démarquer. Quelques exemples: la méthode Format () de la classe String la méthode WriteLine () de la classe Console la méthode Cosh () de la classe Math

Et encore une chose ... La plupart des gens ne s’en soucieront pas, mais si vous le pouvez sur la performance d’un appel indirect, c’est une autre raison pour éviter les méthodes d’instance. Il y a des cas où c'est un coup dur pour la performance. C'est pourquoi les méthodes non virtuelles existent en premier lieu.

zumalifeguard
la source
3
  1. Je pense que c'est en partie parce que les méthodes statiques sont "plus rapides" à appeler que les méthodes d'instance. (Entre guillemets car cela sent l'optimisation micro) voir http://dotnetperls.com/static-method
  2. Cela vous dit qu'il n'a pas besoin d'état, donc qu'il peut être appelé de n'importe où, ce qui supprime la surcharge d'instanciation si c'est la seule chose dont quelqu'un a besoin.
  3. Si je veux m'en moquer, alors je pense que c'est généralement la pratique qui est déclarée sur une interface.
  4. Si cela est déclaré sur une interface, R # ne vous suggérera pas de la rendre statique.
  5. S'il est déclaré virtuel, R # ne vous proposera pas non plus de le rendre statique.
  6. La conservation statique de l’état (des champs) doit toujours être considérée avec soin . L'état statique et les fils se mélangent comme le lithium et l'eau.

R # n'est pas le seul outil qui fera cette suggestion. L'analyse de code FxCop / MS fera également de même.

Je dirais généralement que si la méthode est statique, elle devrait généralement pouvoir être testée telle quelle. Cela amène quelques considérations de conception et probablement plus de discussion que je n’en ai entre les doigts pour le moment, alors attendez patiemment les votes négatifs et les commentaires ...;)

MIA
la source
Pouvez-vous déclarer une méthode / un objet statique sur une interface? (Je ne pense pas). Ether, je fais référence au moment où votre méthode statique est appelée par la méthode testée. Si l'appelant se trouve dans une unité différente, la méthode statique doit être isolée. C'est très difficile à faire avec l'un des trois outils énumérés ci-dessus.
Vaccano
1
Non, vous ne pouvez pas déclarer statique sur une interface. Cela n'aurait aucun sens.
MIA
3

Je vois qu'après longtemps personne n'a encore énoncé un fait très simple. Si le revendeur me dit que je peux rendre une méthode statique, cela signifie une chose énorme pour moi, je peux entendre sa voix me dire: "Hé, toi, ces éléments de la logique ne sont pas de la responsabilité de la classe actuelle à gérer, donc ils doivent rester en dehors dans une classe d'assistance ou quelque chose ".

g1ga
la source
2
Je ne suis pas d'accord. La plupart du temps, quand Resharper dit que je peux rendre quelque chose de statique, c'est un peu de code commun à deux ou plusieurs méthodes de la classe. Je l'ai donc extrait dans sa propre procédure. Transférer cela dans un assistant serait une complexité insignifiante.
Loren Pechtel
2
Je ne peux comprendre votre point que si le domaine est très simple et qu'il ne convient pas pour une modification future. De manière différente, ce que vous appelez "complexité dénuée de sens" est pour moi un bon design lisible par l'homme. Avoir une classe d'assistance avec une raison simple et claire d'exister est en quelque sorte le "mantra" des principes SoC et Single Responsibility. De plus, considérant que cette nouvelle classe devient une dépendance pour la principale, elle doit exposer certains membres du public et, par conséquent, elle peut être testée de manière isolée et facile à se moquer lorsqu'elle agit comme une dépendance.
g1ga
1
Pas pertinent pour la question.
Igby Largeman
2

Si la méthode statique est appelée depuis une autre méthode , il est impossible d'empêcher ou de substituer un tel appel. Cela signifie que ces deux méthodes forment une seule unité. Les tests unitaires de tous types les testent tous les deux.

Et si cette méthode statique communique avec Internet, connecte des bases de données, affiche des fenêtres contextuelles d'interface graphique ou convertit le test d'unité en désordre total, elle ne nécessite aucun travail simple. Une méthode qui appelle une telle méthode statique n'est pas testable sans refactoring, même si elle contient beaucoup de code purement informatique qui bénéficierait grandement d'un test unitaire.

h22
la source
0

Je pense que Resharper vous donne la confiance et applique les directives de codage avec lesquelles il a été configuré. Lorsque j'ai utilisé Resharper et qu'il m'a indiqué qu'une méthode devrait être statique, elle doit nécessairement être sur une méthode privée qui n'agit sur aucune variable d'instance.

Maintenant, en ce qui concerne la testabilité, ce scénario ne devrait pas être un problème car vous ne devriez de toute façon pas tester les méthodes privées.

En ce qui concerne la testabilité des méthodes statiques publiques, les tests unitaires deviennent difficiles lorsque les méthodes statiques entrent en contact avec un état statique. Personnellement, je le limiterais au minimum et utiliserais autant que possible des méthodes statiques sous forme de fonctions pures, dans lesquelles toutes les dépendances seraient transmises à la méthode et contrôlables via un appareil de test. Cependant, ceci est une décision de conception.

aqwert
la source
Je fais référence à quand vous avez une méthode sous test (pas statique) qui appelle une méthode statique dans une autre classe. Cette dépendance ne peut être isolée qu'avec l'un des trois outils énumérés ci-dessus. Si vous ne l'isolez pas, vous effectuez des tests d'intégration et non des tests unitaires.
Vaccano
L'accès aux variables d'instance signifie de manière inhérente qu'il ne peut pas être statique. Le seul état qu'une méthode statique puisse avoir est celui des variables de classe et la seule raison qui devrait en résulter est que vous utilisez un singleton sans avoir le choix.
Loren Pechtel