2011-06-03 8 views
3

donc j'ai quelque chose le long des lignes de ceComment tester une méthode publique que l'accès d'un membre privé

private List<ConcurrentQueue<int>> listOfQueues = new List<ConcurrentQueue<int>>() 
public void InsertInt(int id, int value) 
{ 
    listOfQueues[id].Enqueue(value); 
} 

Est-ce quelque chose que je ne devrais pas être tests unitaires?
Si c'est comment puis-je tester la méthode InsertInt sans utiliser d'autres méthodes? est-il correct d'utiliser la méthode pour récupérer les données de la file d'attente pour tester si le il était entré correctement? Est-ce que c'est quelque chose que je devrais utiliser pour les mocks?

Répondre

2

Vous ne souhaitez généralement pas tester les membres privés. Le point de test de l'unité est que de l'extérieur, la classe prend les entrées spécifiées et (si demandé) vous donnera les sorties correctes. Vous ne vous souciez pas de la mise en œuvre de comment il vous donne ces sorties, vous vous souciez juste qu'il vous donne les bonnes sorties pour une utilisation externe. Pour montrer cela, dites que vous créez un test unitaire vérifie que la méthode InsertInt() insère l'int à listOfQueues. Ensuite, vos besoins changent, et vous devez changer votre stratégie d'implémentation, et au lieu d'utiliser un List<ConcurrentQueue<int>>, il devient un Dictionary<string, ConcurrentQueue<int>>. Cela ne nécessitera peut-être pas de modification de vos entrées, et vos sorties peuvent encore passer n'importe quelle vérification de sortie, mais votre test unitaire InsertInt() échouera car il est codé en dur à l'implémentation. La meilleure idée est de faire des tests unitaires qui s'assurent que si vous appelez InsertInt() avec l'entrée correcte, que vos méthodes de sortie retourneront toujours la sortie corrigée, ainsi que de créer des tests unitaires qui appellent InsertInt() avec des paramètres non valides provoque des exceptions . Vous pouvez ensuite tout changer à propos de votre implémentation interne et être sûr que votre classe fonctionne toujours correctement. L'unité de test de la mise en œuvre ajoute une surcharge supplémentaire tout en offrant très peu d'avantages en termes de testabilité. Notez que je ne dis pas que cette méthode ne devrait pas être testée à l'unité, c'est juste que les tests unitaires doivent être développés de manière à refléter l'interaction des objets extérieurs avec votre classe.

+0

Donc c'est mieux pour tester plusieurs choses dans un test unitaire, que de lier le test unitaire à une implémentation? – scott

+0

Oui, je le crois parce qu'il imite exactement comment cette classe sera utilisée dans des scénarios d'utilisation réels. Si vous testez les deux méthodes indépendamment, vous n'avez pas de test unitaire qui vérifie que les méthodes d'entrée et de sortie fonctionnent réellement ensemble, c'est juste une hypothèse. Vous pouvez alors changer l'implémentation (et le test) d'un et pendant que vos tests unitaires passeront mais que le code échouera en production. Vous avez encore besoin d'un test unitaire que si vous appelez la méthode d'entrée avec des paramètres x, votre sortie vous donne les paramètres y, donc les tests privés ont été une perte de temps et d'efforts. – KallDrexx

+0

En outre, il s'agit toujours de tests unitaires car votre test unitaire ne traite que de la classe elle-même et ne sort pas de la classe, donc gérer plusieurs méthodes n'est pas une mauvaise chose. – KallDrexx

1

Comme cette méthode est une méthode publique, elle doit faire l'objet de tests unitaires. Un rapide coup d'oeil à votre méthode révélerait que passer -1 comme la valeur de id causerait un ArgumentOutOfRangeException. Cela aurait été réalisé au cours du codage si le test élémentaire de cette méthode avait été conçu (en supposant que de nombreuses méthodes existent).

Pour vérifier si l'insertion est réussie, vous pouvez utiliser la méthode indiquée par @Oskar Kjellin.

Si vous souhaitez vous salir, vous pouvez utiliser Reflection pour vérifier si la valeur a été insérée ou non.

// Sample with Queue instead of ConcurrentQueue 
private void TestInsertInt(int id, int value) 
{ 
    myInstance.InsertInt(id, value); 

    FieldInfo field = myInstance.GetType().GetField("listOfQueues", BindingFlags.NonPublic | BindingFlags.Instance); 
    List<Queue<int>> listOfQueues = field.GetValue(myInstance) as List<Queue<int>>; 
    int lastInserted = listOfQueues[id].Last(); 

    Assert.AreEqual(lastInserted, value); 
} 
+0

c'est ce que j'ai senti aussi. Donc je suppose que la question la plus importante est quelle est la bonne façon de le faire? – scott

+0

Vous n'avez pas besoin de tester des membres privés pour créer un test unitaire qui passe dans un ID de zéro provoquera une 'ArgumentOutOfRangeException' – KallDrexx

2

Oui, vous devez tester l'unité. Vous pouvez utiliser un accesseur privé pour obtenir listOfQueues. Vous devez vous assurer avec un test unitaire que la méthode se comporte comme prévu avec des exceptions et que l'élément est réellement inséré.

Vérifiez cet article sur la façon de méthodes privées test unitaire http://msdn.microsoft.com/en-us/library/ms184807(v=vs.80).aspx

+0

Suite à l'article listé, Visual Studio vous demandera si vous souhaitez qu'un accesseur privé soit ajouté au Assemblée. Vous pouvez le faire manuellement en ajoutant la ligne suivante à la page AssemblyInfo.cs [assembly: System.Runtime.CompilerServices.InternalsVisibleTo (""]] – JMcCarty

+0

InternalsVisibleTo ne rend que les éléments internes visibles - pas privés? L'article ci-dessus génère des wrappers de réflexion autour de méthodes privées que vous pouvez ensuite compiler dans votre assembly de test. –

+0

est-il un moyen de générer un accesseur privé pour un membre pas une méthode? – scott

0

Est-ce quelque chose que je ne devrais pas être unité test?

Si vous ciblez la couverture de test 100% alors oui

Si c'est comment puis-je tester la méthode InsertInt sans utiliser d'autres méthodes ?

Plusieurs options:

  • Utilisez une méthode accesseur différente que vous suggérez.
  • Rendre le contenant interne et donner l'accès de l'ensemble des tests unitaires aux internes
  • facteur conteneur dehors dans une classe séparée que vous passez comme une dépendance à cette classe, puis à passer votre test unitaire d'un conteneur maquette au lieu
  • Utiliser la réflexion pour accéder aux membres privés
0

Un autre hack est d'utiliser le PrivateObject.

Personnellement, j'utiliserais DI (Dependency Injection) ou ferais le private, internal pour des tests unitaires!

1

Vous ne devriez pas tester le comportement des files d'attente - il s'agit d'un détail d'implémentation que vous pouvez modifier sans modifier le comportement de la méthode. Par exemple, vous pouvez remplacer la fonction ConcurrentQueue par une autre structure de données, peut-être une arborescence, sans avoir besoin de mettre à jour les tests unitaires.

Ce que vous testez, c'est que cette méthode accepte les entrées et stocke la valeur comme prévu. Par conséquent, vous aurez besoin d'une certaine façon d'interroger l'état du système tel que

public int GetInt(int id) 

Ensuite, vous tester que les inserts de méthode que vous attendez en les récupérant en utilisant le même identifiant.

Vous devez tester que la méthode publique renvoie les résultats attendus dans tous les cas auxquels vous pensez, et laissez la méthode stocker les valeurs comme bon vous semble. Par conséquent je testerais probablement la méthode comme ceci, avec différentes entrées:

 [TestCase(1,2,3)] // whatever test cases make sense for you 
     [TestCase(4,5,6)] 
     [TestCase(7,8,9)] 
     [Test] 
     public void Test_InsertInt_OK(int testId, int testValue, int expectedValue) 
     { 
      InsertInt(testId, testValue); 
      Assert.AreEqual(GetInt(testId), expectedValue) 
     } 
Questions connexes