2009-05-27 10 views
6

J'essaie de tester une méthode d'entité Order appelée AddItem et j'essaie de m'assurer que les éléments en double ne peuvent pas être ajoutés. Voici quelques exemples de code:Question de Setter privé de test d'unité (C#)

[Test] 
public void ItemCannotBeAddedTwiceToOrder() 
{ 
    Order o = new Order(); 
    Item i = new Item("Bike"); 

    o.AddItem(i); 
    o.AddItem(i); 

    Assert.AreEqual(o.ItemCount, 1, "A duplicate item was added."); 
} 

public void AddItem(Item newItem) 
{ 
    if(!CheckForDuplicateItem(newItem)) 
     _items.Add(newItem); 
} 

public bool CheckForDuplicateItem(Item newItem) 
{ 
    foreach(Item i in _items) 
    { 
     if(i.Id == newItem.Id) 
      return true; 
    } 

    return false; 
} 

Voici donc mon problème: comment puis-je configurer setter privé du nouvel article Id dans la méthode d'essai de sorte que la méthode CheckForDuplicateItem fonctionnera? Je ne veux pas rendre public ce membre pour de bonnes pratiques de codage, je suppose. Est-ce que je suis juste stupide et ai besoin de faire que l'élément d'entité ait un régleur d'identité public? Ou dois-je utiliser la réflexion? Merci

Remarque - J'utilise NHibernate pour la persistance

Répondre

8

J'utilise habituellement la réflexion à cet effet. Quelque chose comme ça va fonctionner:

typeof(Item).GetProperty("Id").SetValue(i, 1, null); 

où 1 est l'identifiant que vous souhaitez définir pour l'instance newItem. Dans mon expérience, vous aurez rarement besoin de définir l'Id, il est donc préférable de laisser le setter privé. Dans les rares cas où vous devez définir l'ID à des fins de test, utilisez simplement Reflection.

+0

Merci beaucoup. Je vais essayer. – CalebHC

7

Puisque vous vérifiez le comportement de votre commande, vous pouvez utiliser des objets simulés comme éléments. En utilisant des objets fantaisie, vous pouvez définir vos affirmations sur ce qui va arriver à vos objets fantaisie et les tester aussi. Dans ce cas, vous pouvez définir deux objets fantaisie pour chacun des éléments et attendez que leur ID getter sera appelé et retournera une valeur unique. Ensuite, vous pouvez tester le comportement Order et vérifier si id getter de l'élément est appelé comme vous attendu. Je recommande d'utiliser Rhino Mocks par Ayende

+0

Bon point. Je vais regarder en se moquant. J'ai entendu beaucoup de bonnes choses à propos de Rhino Mocks. – CalebHC

0

Une autre solution consiste à rendre les membres privés accessibles en dérivant de la classe et en exposant le membre dans la classe dérivée. C'est beaucoup de frais généraux pour les tests et Visual Studio a seulement build-in support for private methods.

0

Je pense que vous pourriez manquer le point ici. Evitez-vous plusieurs ajouts parce que vous ne voulez pas plusieurs appels à la base de données? Je pense que NHibernate vous le donne gratuitement. Alternativement, devriez-vous utiliser un Set? Quelles sont les implications pour l'appelant qu'un article pourrait ou ne pourrait pas être ajouté?

S'il n'y a pas de problème de persistance, vous pouvez simplement ajouter deux éléments distincts avec le même ID et confirmer que vous avez seulement le premier. Il doit y avoir un moyen de détecter quels éléments sont dans l'ordre, ou il serait inutile de les ajouter ...

3

Alors que la réponse de Praveen est correcte et sert sans doute pour l'usage unique, il manque un certain type de sécurité à utilisez ceci dans des tests sur un modèle de domaine fort encore et encore. Par conséquent, j'enveloppa dans une méthode d'extension qui vous permet ce type appel de sécurité pour définir une valeur:

var classWithPrivateSetters= new ClassWithPrivateSetters(); 
classWithPrivateSetters.SetPrivate(cwps => cwps.Number, 42); 

déposer ce dernier dans votre assemblage d'essai et vous êtes bon pour aller

public static class PrivateSetterCaller 
{ 
    public static void SetPrivate<T,TValue>(this T instance, Expression<Func<T,TValue>> propertyExpression, TValue value) 
    { 
     instance.GetType().GetProperty(GetName(propertyExpression)).SetValue(instance, value, null); 
    } 

    private static string GetName<T, TValue>(Expression<Func<T, TValue>> exp) 
    { 
     MemberExpression body = exp.Body as MemberExpression; 

     if (body == null) 
     { 
      UnaryExpression ubody = (UnaryExpression)exp.Body; 
      body = ubody.Operand as MemberExpression; 
     } 

     return body.Member.Name; 
    } 
}