2 votes

Comment concevoir des méthodes pour éviter de les rendre non testables ?

Je veux tester en unité une méthode d'extension pour un type WebSiteResource où ce type WebSiteResource est une mise en œuvre d'un abstract type. J'ai besoin de définir ou de simuler une valeur pour une propriété, mais ce n'est pas virtual .

public static string HostName(this WebSiteResource webSite) => webSite.Data.DefaultHostName;
  • Je ne peux pas me moquer WebSiteResource parce qu'il ne s'agit pas d'une interface
  • Je ne peux pas instancier WebSiteResource parce que la propriété Data nécessite une configuration complexe
  • Je ne peux pas créer un MockableWebSiteResource qui hérite de WebSiteResource (et faire quelque chose comme public new virtual Data { get; set; } ) car Data nécessite une configuration complexe

Comment concevoir des méthodes pour éviter de les rendre non testables ?

Mise à jour (après la réponse) :

Ce que j'ai en fait, c'est une méthode non triviale du type

public static bool IsValid(this WebSiteResource webSite) {
   // and then complex logic for multiple properties etc
}

3voto

Dai Points 24530

Je veux tester en unité une méthode d'extension...

Le site string HostName() La méthode que vous avez postée est une fonction triviale : Je ne pense pas qu'il soit utile d'écrire un quelconque code de test pour cela. .

Les tests automatisés sont formidables, mais ils introduisent leur propre fragilité et leur propre charge de maintenance. S'il vous a fallu 3 minutes pour rechercher et écrire cette fonction, il vous faudra désormais beaucoup plus de minutes supplémentaires pour réfléchir à tous les différents cas de figure étranges et exotiques où votre HostName échouera (par exemple, la fonction website.Data is null dans une situation obscure de réseau ou de condition de course).


Ne pas tenir compte de cela spécifique mais d'une manière générale : en ce qui concerne la manière de concevoir testable du code qui consomme directement des objets provenant de bibliothèques externes opaques... eh bien, la réponse simple est la suivante Ne le fais pas. (comme dans : n'écrivez pas de code d'application qui consomme directement des objets de bibliothèques externes et qui passe directement ces objets plus loin dans votre propre code d'application ).

Alors, mettez plutôt en place des limites claires : cloisons logicielles entre des composants distincts dans le code de votre application et ne laissez jamais un objet externe nu franchir cette ligne. Si vous avez besoin de faire passer des données d'une bibliothèque externe à travers la ligne, alors introduisez l'option votre propre POCO data-transfer-object et copier les valeurs dedans, puis passer que le long.

L'alternative chemin de moindre résistance n'aboutit qu'à de plus mauvais résultats à long terme : au bout du compte, chaque projet de votre solution doit référencer tous les mêmes paquets NuGet et les mêmes références d'assemblage afin que les objets de bibliothèque externes puissent être transmis (par exemple, en transmettant le fichier WebSiteResource jusqu'à votre interface utilisateur).

.... alors vous rencontrerez de plus en plus de problèmes comme ceux que vous avez illustrés : même si c'est votre projet vous n'avez plus aucun contrôle réel sur les objets opaques que vous utilisez : afin d'accomplir un objectif futur, vous vous retrouverez probablement à combattre ces librairies et à devoir écrire du code pirate pour les contourner au lieu de simplement éditer directement leur source et les recompiler, parce que vous ne les contrôlez pas.


Aparté : Personnellement, je blâme le système de type inflexible de C#...

Je note qu'une grande partie de ce problème est simplement dû à la façon dont C# et le CLR .NET fonctionnent : cela en fin de compte, vous ne pouvez pas manipuler cette WebSiteResource (et ses objets-instances) par le système de type .NET. (...au moins en Java, tout est <code>virtual</code> ) .

Parce que C#/.NET utilise un système de type nominatif qui est strictement appliqué par le runtime, et parce qu'il ne supporte pas le typage structurel ou les véritables types algébriques, cela signifie que nous ne pouvons pas faire des choses comme class HahahImSubclassingYouAnyway extends WebSiteResource (ou même class HahahImSubclassingYouAnyway implements WebSiteResource dans certaines langues). Au moins, nous pouvons utiliser la composition (que nous devrait Mais une fois de plus, C#/.NET traîne encore les pieds lorsqu'il s'agit de faciliter la composition de types, sans parler de la façon dont il est possible d'utiliser l'outil de composition. impossible pour maintenir l'identité de la référence (c'est pourquoi nous sommes obligés d'utiliser la sous-classification basée sur l'héritage dans certaines situations).

Bref, je divague...


Je ne peux pas me moquer WebSiteResource parce qu'il ne s'agit pas d'une interface

  • En supposant que vous Je suis toujours d'accord avec ça. puis implémentez vos propres types d'adaptateurs.
    • Oui, c'est exactement aussi fastidieux et source d'erreurs que ça en a l'air.
    • Oui c'est pourquoi c'est probablement une utilisation inappropriée de votre temps de travailler sur ce sujet.

Je ne peux pas instancier WebSiteResource parce que la propriété Data nécessite une configuration complexe

Le fait que vous ayez écrit cela donne l'impression que vous envisagez d'écrire des cas de test qui testeront essentiellement le WebSiteResource bien plus que votre propre code. Je sais que ce n'est pas votre intention, cependant.

Je ne peux pas créer un MockableWebSiteResource qui hérite de WebSiteResource (et faire quelque chose comme public new virtual Data { get; set; } ) car Data nécessite une configuration complexe

Whoa, stop, Ne bougez pas. ...

Ce que vous décrivez est un abus de l'héritage OOP. Votre proposition MockableWebSiteResource ne devrait clairement pas avoir de relation conceptuelle "est" avec WebSiteResource mais c'est exactement ce que représente l'héritage des classes dans le cadre de la POO. WebSiteResource représente une mise en œuvre de service complète, et non un service abstrait). C'est un signe que quelque chose ne va pas avec votre approche. Arrêtez. Réfléchissez. Reconsidérez la situation.

(Et parce que Java -L'héritage OOP de style (que C# partage) est si limitatif et inélégant qu'il finit juste par créer les problèmes au lieu de les résoudre : c'est tout simplement mauvais . Je suis sûr que vous avez rencontré des situations où il était impossible d'utiliser l'héritage de la POO pour tout type d'activité directe. modélisation de domaine ).

(Avec mes excuses aux fans de Smalltalk et de Simula (nous vous aimons), mais j'utilise le terme "POO" dans le sens de la famille Java : je ne fais donc pas référence à l'approche plus large (ou à la <em>plus pur </em>) les concepts supérieurs de la POO O.G. où le passage de messages et la délégation sont utilisés au lieu des méthodes virtuelles et de l'héritage)

Comment concevoir des méthodes pour éviter de les rendre non testables ?

Je ne peux rien vous donner d'autre que des directives générales que nous avons tous déjà données :

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X