1
votes

Toutes les méthodes publiques doivent-elles être déclarées dans l'interface?

Étant donné une interface comme:

public class Foo : IFoo
{
    public string Bar()
    {
        returns "Bar";
    }

    public string SomeOtherMethod()
    {
        returns "Something else";
    }
}

Et une classe qui l'implémente comme:

public interface IFoo
{
    string Bar();
}

Y a-t-il un problème avec ce code? Devrions-nous ajouter tous les membres à l'interface?

Un exemple: imaginez une méthode privée qui devient suffisamment complexe pour justifier des tests unitaires. Pourriez-vous le rendre public (afin qu'il puisse être appelé à partir du projet de test) mais ne pas l'ajouter à l'interface (car aucun client n'a besoin de l'appeler)?


7 commentaires

Expérience de réflexion: Les classes sont libres d'implémenter plus d'une interface, ou aucune interface du tout. Si ma classe n'implémente aucune interface, est-ce que cela rend la classe inutilisable?


Pourriez-vous le rendre public (afin qu'il puisse être appelé à partir du projet de test) mais ne pas l'ajouter à l'interface (car aucun client n'a besoin de l'appeler)? - Oui.


Des tonnes de classes implémentent IDisposable . Si la seule méthode publique dont ils disposaient était Dispose , ils ne seraient pas très utiles.


Je crée généralement des méthodes privées que je souhaite tester en interne, pas en public


De pair avec ce que Robert dit, c'est le I dans SOLID pour la ségrégation des interfaces: codeburst.io/...


Tester des méthodes qu'un programme normal ne peut jamais appeler est une pente assez glissante. Cela n'a pas grand-chose à voir avec les tests, tout à voir avec les diagnostics «où est le bogue». Vous utilisez un débogueur pour cela, cela ne vous dérange pas de définir un point d'arrêt sur une méthode privée ou interne. Le test unitaire est un excellent moyen de déclencher ce point d'arrêt, assurez-vous que vous êtes à l'aise avec l'utilisation d'un débogueur dans un test unitaire.


Les tests unitaires ne doivent pas cibler les méthodes privées car les méthodes privées sont des détails d'implémentation et devraient donc être libres d'être refactorisées même sans aucune modification publique de votre classe. Vous ne devez tester les membres privés que via les membres publics qui les utilisent.


3 Réponses :


2
votes

En général, lorsqu'une classe implémente une interface, tous les membres d'une interface doivent être implémentés, cela ne va pas l'inverse.

De même, lors de l'implémentation d'un membre d'interface, le membre correspondant de la classe d'implémentation doit être public, non statique et avoir le même nom et la même signature de paramètre que le membre d'interface. Vous pouvez avoir des méthodes, même publiques, dans la classe qui ne sont pas définies dans l'interface.

En plus de cela, l'interface elle-même ne fournit aucune fonctionnalité dont une classe ou une structure peut hériter de la manière dont elle peut hériter des fonctionnalités de classe de base. Cependant, si une classe de base implémente une interface, toute classe dérivée de la classe de base hérite de cette implémentation.


0 commentaires

1
votes

Si ce qu'une classe fait est assez simple pour que nous puissions tester ses méthodes publiques et ses méthodes privées en même temps, alors nous pouvons simplement tester les méthodes publiques.

Si la méthode privée devient si complexe qu'entre le public et méthode privée nous avons besoin de trop de combinaisons de tests, alors il est temps de séparer la méthode privée dans sa propre classe. Rendre la méthode privée publique interromprait l'encapsulation de la classe. Même si nous n'ajoutons pas la méthode à une interface , rendre la méthode de classe publique ajoute toujours la méthode à l'interface publique de la classe elle-même .

Donc si nous avons ceci:

public class RefactoredClass
{
    private readonly Func<int, string> _dependency;

    public RefactoredClass(Func<int, string> dependency)
    {
        _dependency = dependency;
    }

    public string DoSomething(int value)
    {
        return _dependency(value);
    }
}

Nous pourrions refactoriser quelque chose comme ceci:

public interface IRepresentsWhatThePrivateMethodDid
{
    string MethodThatNeedsItsOwnTests(int value);
} 

public class RefactoredClass
{
    private readonly IRepresentsWhatThePrivateMethodDid _dependency;

    public RefactoredClass(IRepresentsWhatThePrivateMethodDid dependency)
    {
        _dependency = dependency;
    }

    public string DoSomething(int value)
    {
        return _dependency.MethodThatNeedsItsOwnTests(value);
    }
}

Et maintenant un nouveau La classe implémente IRepresentWhatThePrivateMethodDid.

Maintenant, lorsque nous testons la classe refactorisée, nous nous moquons de IRepresentWhatThePrivateMethodDid , et nous écrivons des tests unitaires séparés pour toutes les classes qui implémentent IRepresentWhatThePrivateMethodDid .

Cela peut sembler contradictoire de dire qu'exposer la méthode privée en tant que public rompt l'encapsulation, mais l'exposer comme sa propre classe séparée ne le fait pas. Il y a deux différences:

  • La classe refactorisée ne dépend pas de la nouvelle classe contenant ce qui était auparavant la méthode privée. Cela dépend de la nouvelle interface.
  • L'interaction entre la classe refactorisée et l'interface est toujours masquée dans ses méthodes. Les autres classes qui appellent ses méthodes publiques ne "savent" pas comment il utilise sa dépendance. (En fait, d'autres classes dépendront probablement des abstractions plutôt que directement de la classe refactorisée.)

Il est également facile de se laisser emporter par cela et d'introduire la dépendance séparée trop tôt, alors que nous aurions pu tester la classe - y compris ses méthodes privées - via les méthodes publiques. Je l'ai fait beaucoup de fois, et cela peut entraîner beaucoup d'interfaces inutiles et de classes supplémentaires. Il n'y a pas de perfection, juste nos meilleurs efforts pour l'équilibrer.


Encore une réflexion: nous avons tendance à utiliser des interfaces pour représenter les dépendances, mais ce n'est pas nécessaire. Si ce que nous extrayons n'était qu'une seule méthode privée, nous pourrions peut-être le représenter avec un délégué ou un Func à la place, comme ceci:

public class ClassWithComplexPrivateMethod
{

    public void DoSomething(int value)
    {
        PrivateMethodThatNeedsItsOwnTests(value);
    }

    private string PrivateMethodThatNeedsItsOwnTests(int value)
    {
        // This method has gotten really complicated!
        return value.ToString();
    }
}

Ou nous pouvons utiliser un délégué, que j'aime mieux que Func car il indique ce que fait la fonction.


0 commentaires

0
votes

L'interface doit représenter uniquement l'API requise sans tenir compte des tests unitaires et autres.


Pour tester les méthodes publiques de classe, vous ne devez faire rien car Test-project y a accès. Il vous suffit d'instancier une classe et d'injecter toutes les dépendances requises à l'aide de Mock.

Exemple: test de classe sans aucune dépendance

Exemple: test de classe avec dépendance


Pour tester les méthodes privées d'une classe, il faut les rendre visibles à test-project:


0 commentaires