web-dev-qa-db-fra.com

La statique est-elle universellement "mauvaise" pour les tests unitaires et si oui, pourquoi Resharper le recommande-t-il?

J'ai constaté qu'il n'y a que 3 façons de tester les dépendances (mock/stub) qui sont statiques dans C # .NET:

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

Cela fait-il des méthodes statiques et un tel "mal" (dans le sens des tests unitaires)? Et si oui, pourquoi Resharper veut-il que je fasse quelque chose qui puisse être statique, statique? (En supposant que le resharper n'est pas aussi "mal".)

Clarification: Je parle du scénario lorsque vous voulez tester une méthode et que cette méthode appelle une méthode statique dans un différent unité/classe. Selon la plupart des définitions des tests unitaires, si vous laissez simplement la méthode testée appeler la méthode statique dans l'autre unité/classe, vous n'êtes pas pas test unitaire, vous testez l'intégration. (Utile, mais pas un test unitaire.)

87
Vaccano

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

Les méthodes statiques qui ne possèdent aucun état et ne provoquent aucun effet secondaire devraient être facilement testables à l'unité. En fait, je considère ces méthodes comme une forme de programmation fonctionnelle "du pauvre"; vous remettez à la méthode un objet ou une valeur, et elle retourne un objet ou une valeur. Rien de plus. Je ne vois pas du tout comment de telles méthodes pourraient affecter négativement les tests unitaires.

108
Robert Harvey

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

Il n'y a rien de mal avec les méthodes statiques et elles sont faciles à tester (tant qu'elles ne modifient aucune donnée statique). 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 aucun effet secondaire. Vérifiez simplement que lorsque vous passez, disons 20, vous en récupérez 400. Pas de problème.

27
Dan Diplo

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

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

Ensuite, refactorisez simplement le code et injectez 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();
    }
}
18
Sunny

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

Il existe deux approches générales de la moquerie.

Le premier (traditionnel - implémenté par RhinoMocks, Moq, NMock2; les mocks et stubs manuels sont également dans ce camp) repose sur des coutures de test et une injection de dépendance. Supposons que vous testiez un peu de code statique et qu'il ait des dépendances. Ce qui se produit souvent dans le code conçu de cette manière, c'est que la statique crée ses propres dépendances, inversant l'inversion de dépendance . Vous découvrez bientôt que vous ne pouvez pas injecter d'interfaces simulées dans du code sous test conçu de cette façon.

Le second (se moquer de tout - implémenté par TypeMock, JustMock et Moles) repose sur Profiling API de .NET. Il peut intercepter n'importe laquelle de vos instructions CIL et remplacer un morceau de votre code par un faux. Cela permet à TypeMock et à d'autres produits de ce camp de se moquer de tout: statique, classes scellées, méthodes privées - des choses non conçues pour être testables.

Il y a un débat en cours entre deux écoles de pensée. On dit, suivez les principes SOLIDES et la conception pour la testabilité (qui comprend souvent aller doucement sur la statique). L'autre dit, achetez TypeMock et ne vous inquiétez pas.

15
azheglov

Vérifiez ceci: "Les méthodes statiques sont mortelles pour la testabilité" . Bref résumé de l'argument:

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

14
Rafał Dowgird

La simple vérité rarement reconnue est que si une classe contient une dépendance visible par le compilateur d'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 sur un rapport comme s'il s'agissait d'un test.

Mais il n'aura pas les principales propriétés définissant un test; échouer quand les choses vont mal, passer quand elles vont bien.

Cela s'applique à tous les appels statiques, 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 classe apparaît dans le code, il s'agit d'une dépendance visible par le compilateur et vous ne pouvez pas valider correctement sans lui. Tout bloc plus petit est simplement pas une unité testable valide. Toute tentative de le traiter comme s'il avait des résultats n'aura pas plus de sens que d'écrire un petit utilitaire pour émettre le XML utilisé par votre framework de test pour dire "test réussi".

Cela étant, il existe trois options:

  1. définir 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 au moment de la compilation entre les classes que vous êtes responsable de tester. Cela fonctionne, à condition que le style de code résultant ne vous dérange pas.

  3. ne faites pas de test unitaire, testez plutôt l'intégration. Ce qui fonctionne, à condition qu'il n'entre pas en conflit avec autre chose pour laquelle vous devez utiliser le terme test d'intégration.

5
soru

Il n'y a pas deux façons. Les suggestions de ReSharper et plusieurs fonctionnalités utiles de C # ne seraient pas utilisées aussi souvent si vous écriviez des tests unitaires atomiques isolés pour tout votre code.

Par exemple, si vous avez une méthode statique et que vous devez la supprimer, vous ne pouvez pas sauf si vous utilisez une infrastructure d'isolation basée 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 bloquer cet appel comme ceci (en supposant qu'il se trouve dans une classe appelée base de données):

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

Soyez prudent de le remplacer par la valeur d'origine après avoir terminé. Vous pouvez le faire via un essai/enfin ou, dans votre nettoyage de test unitaire, 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 Func Lambda ne sont pas aussi riches en support que les méthodes statiques classiques, donc cette approche a les effets secondaires indésirables suivants:

  1. Si la méthode statique était une méthode d'extension, vous devez d'abord la changer en une méthode sans extension. Resharper peut le faire automatiquement pour vous.
  2. Si l'un des types de données des méthodes statiques est un assembly à interopérabilité intégrée, comme pour Office, vous devez encapsuler la méthode, encapsuler le type ou le modifier pour taper "objet".
  3. Vous ne pouvez plus utiliser l'outil de refactorisation de signature de changement de Resharper.

Mais disons que vous évitez complètement la statique et que vous la convertissez en méthode d'instance. Il n'est toujours pas moquable à moins que la méthode soit virtuelle ou implémentée dans le cadre d'une interface.

Donc, en réalité, quiconque suggère que le remède à la suppression des méthodes statiques consiste à en faire des méthodes d'instance, il serait également contre les 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 des méthodes d'instance non virtuelles?

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 n'importe quel code que vous ne vous attendez pas à ce que quiconque veuille jamais supprimer. 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 se soucient pas de cela, mais si vous le pouvez sur les performances d'un appel indirect, c'est une autre raison d'éviter les méthodes d'instance. Il y a des cas où c'est un coup de performance. C'est pourquoi les méthodes non virtuelles existent en premier lieu.

4
zumalifeguard

Je vois qu'après longtemps, personne n'a encore énoncé un fait vraiment simple. Si resharper me dit que je peux rendre une méthode statique, cela signifie une chose énorme pour moi, j'entends sa voix me dire: "hé, vous, ces éléments de logique ne sont pas la RESPONSABILITÉ de la classe actuelle à gérer, donc elle doit rester en dehors dans une classe auxiliaire ou quelque chose ".

3
g1ga
  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 la micro-optimisation) voir http://dotnetperls.com/static-method
  2. Cela vous dit qu'il n'a pas besoin d'état, donc pourrait être appelé de n'importe où, supprimant les frais généraux d'instatiation si c'est la seule chose dont quelqu'un a besoin.
  3. Si je veux me moquer de lui, alors je pense que c'est généralement la pratique qu'il est déclaré sur une interface.
  4. S'il est déclaré sur une interface, R # ne vous suggérera pas de le rendre statique.
  5. S'il est déclaré virtuel, R # ne vous suggérera pas non plus de le rendre statique.
  6. Le maintien statique des états (champs) est quelque chose qui doit toujours être considéré attentivement . 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. FxCop/MS Code Analysis fera également de même.

Je dirais généralement que si la méthode est statique, elle devrait généralement être testable telle quelle. Cela apporte une certaine considération de conception et probablement plus de discussions que je n'en ai en ce moment, alors attendez patiemment les votes et commentaires négatifs ...;)

3
MIA

Si la méthode statique est appelée depuis à l'intérieur d'une autre méthode, il n'est pas possible d'empêcher ou de remplacer un tel appel. Cela signifie que ces deux méthodes forment une seule unité; Un test unitaire de toutes sortes les teste tous les deux.

Et si cette méthode statique parle à Internet, connecte des bases de données, affiche des popups GUI ou convertit autrement le test unitaire en désordre complet, cela se fait sans aucun travail facile. Une méthode qui appelle une telle méthode statique ne peut pas être testée sans refactorisation, même si elle contient beaucoup de code purement informatique qui bénéficierait grandement d'être testé unitaire.

2
h22

Je crois que Resharper vous donne des preuves et applique les directives de codage avec lesquelles il a été configuré. Lorsque j'ai utilisé Resharper et qu'il m'a dit qu'une méthode devrait être statique, elle est forcément 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 pas tester les méthodes privées de toute façon.

En ce qui concerne la testabilité des méthodes statiques qui sont publiques, les tests unitaires deviennent difficiles lorsque les méthodes statiques touchent l'état statique. Personnellement, je garderais cela au minimum et utiliserais autant que possible des méthodes statiques en tant que fonctions pures où toutes les dépendances sont passées dans la méthode qui peut être contrôlée via un appareil de test. Cependant, c'est une décision de conception.

0
aqwert