Comment se moquer de la méthode avec un objet codé en dur?


11

Je travaille sur une application à plusieurs couches. Couche d'accès aux données pour récupérer et enregistrer les données de la source de données, logique métier pour manipuler les données, interface utilisateur pour afficher les données à l'écran.

Je fais également des tests unitaires de la couche logique métier. La seule exigence est de tester le flux de la logique de la couche métier. J'utilise donc le framework Moq pour simuler la couche d'accès aux données et tester unitairement la couche de logique métier avec MS Unit.

J'utilise la programmation d'interface pour faire le plus possible découpler la conception afin que le test unitaire puisse être effectué. La couche métier appelle la couche d'accès aux données via l'interface.

Je rencontre un problème lorsque j'essaie de tester l'une des méthodes de logique métier. Cette méthode fonctionne et crée un objet et le transmet à la couche d'accès aux données. Lorsque j'essaie de se moquer de cette méthode de couche d'accès aux données, elle ne peut pas se moquer avec succès.

Ici, j'essaie de créer un code de démonstration pour montrer mon problème.

Modèle:

public class Employee
{
    public string Name { get; set; }
}

Couche d'accès aux données:

public interface IDal
{
    string GetMessage(Employee emp);
}

public class Dal : IDal
{
    public string GetMessage(Employee emp)
    {
        // Doing some data source access work...

        return string.Format("Hello {0}", emp.Name);
    }
}

Couche logique métier:

public interface IBll
{
    string GetMessage();
}

public class Bll : IBll
{
    private readonly IDal _dal;

    public Bll(IDal dal)
    {
        _dal = dal;
    }

    public string GetMessage()
    {
        // Object creating inside business logic method.
        Employee emp = new Employee(); 

        string msg = _dal.GetMessage(emp);
        return msg;
    }
}

Test de l'unité:

[TestMethod]
    public void Is_GetMessage_Return_Proper_Result()
    {
        // Arrange.
        Employee emp = new Employee; // New object.

        Mock<IDal> mockDal = new Mock<IDal>();
        mockDal.Setup(d => d.GetMessage(emp)).Returns("Hello " + emp.Name);

        IBll bll = new Bll(mockDal.Object);

        // Act.

        // This will create another employee object inside the 
        // business logic method, which is different from the 
        // object which I have sent at the time of mocking.
        string msg = bll.GetMessage(); 

        // Assert.
        Assert.AreEqual("Hello arnab", msg);
    }

Dans le cas de test unitaire au moment de la moquerie, j'envoie un objet Employee, mais lorsque j'appelle la méthode de logique métier, cela crée un objet Employee différent à l'intérieur de la méthode. C'est pourquoi je ne peux pas me moquer de l'objet.

Dans ce cas, comment concevoir pour que je puisse résoudre le problème?


Habituellement, l'astuce consiste à envelopper l'objet dans une interface et à faire en sorte que tous les consommateurs en utilisent cette interface, puis vous vous moquez simplement de l'interface, ou bien vous pouvez rendre la méthode virtuelle et ensuite moq peut se moquer de la méthode sans l'interface. Pas certain au sujet des rhinomocks ou d'autres dans ce cas cependant.
Jimmy Hoffa

Réponses:


12

Au lieu de créer un Employeeobjet directement en utilisant new, votre classe Bllpourrait utiliser une EmployeeFactoryclasse pour cela, avec une méthode createInstance, qui est injectée via le constructeur:

 class EmployeeFactory : IEmployeeFactory
 {
       public Employee createInstance(){return new Employee();}
 }

Le constructeur doit prendre l'objet d'usine à travers une interface IEmployeeFactory, de sorte que vous pouvez facilement remplacer la «vraie» usine par une usine factice.

public class Bll : IBll
{
    private readonly IDal _dal;
    private readonly IEmployeeFactory _employeeFactory;

    public Bll(IDal dal, IEmployeeFactory employeeFactory)
    {
        _dal = dal;
        _employeeFactory=employeeFactory;
    }

    public string GetMessage()
    {
        // Object creating inside business logic method
        // *** using a factory ***
        Employee emp = _employeeFactory.createObject(); 
        // ...
    }
    //...
}

L'usine de simulation peut fournir au test tout type d' Employeeobjet dont vous avez besoin pour votre test (par exemple, createInstancepourrait toujours retourner le même objet):

 class MockEmployeeFactory : IEmployeeFactory
 {
       private Employee _emp;

       public MockEmployeeFactory()
       {
          _emp = new Employee();
          // add any kind of special initializing here for testing purposes
       }

       public Employee createInstance()
       {
          // just for testing, return always the same object
          return _emp;
       }
 }

Maintenant, utiliser cette maquette dans votre test devrait faire l'affaire.


Pouvez-vous me donner un exemple de code, afin que je puisse visualiser votre théorie?
DeveloperArnab

@DeveloperArnab: voir ma modification.
Doc Brown

Très utile ...
DeveloperArnab

4

Je le traiterais comme une seule unité à tester.

Tant que vous contrôlez toutes les entrées à partir desquelles l' Employeeobjet est créé, le fait qu'il soit créé dans l'objet testé n'a pas d'importance. Vous avez juste besoin d'une méthode the mock pour retourner le résultat attendu si le contenu de l'argument correspond à l'attente.

Évidemment, cela signifie que vous devez fournir une logique personnalisée pour la méthode fictive. La logique avancée ne peut souvent pas être testée avec des types de moquerie «pour x retour y».

En fait, vous ne devriez pas lui faire retourner un objet différent dans les tests que dans la production, car si vous le faisiez, vous ne testeriez pas le code qui devrait le créer. Mais ce code fait partie intégrante du code de production et devrait donc également être couvert par le cas de test.


Oui, je ne me soucie pas des entrées de la couche d'accès aux données, je veux seulement me moquer de cet objet et renvoyer des données codées en dur afin de pouvoir tester la logique métier. Mais le problème est dû à deux objets Employee différents, je ne peux pas me moquer de la méthode de la couche d'accès aux données.
DeveloperArnab

@DeveloperArnab: Les objets seront différents, mais ils auront un contenu connu. Donc, tout ce que vous devez faire est de faire de la simulation une comparaison personnalisée au lieu de l'identité de l'objet.
Jan Hudec

@DeveloperArnab: Si vous injectez un Employeeobjet différent dans les tests, vous ne testerez pas le code qui le crée normalement. Vous ne devriez donc pas le changer.
Jan Hudec

0

C'est un échec de certains outils de test, vous devez toujours utiliser des interfaces et tout doit être créé de manière à vous permettre d'échanger l'objet basé sur l'interface pour un autre.

Cependant, il existe de meilleurs outils - prenez Microsoft Fakes (était appelé Moles) qui vous permet d'échanger n'importe quel objet, même statique et global. Il faut une approche de plus bas niveau pour remplacer les objets afin que vous n'ayez pas à utiliser les interfaces partout tout en conservant la façon d'écrire les tests auxquels vous êtes habitué.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.