Jak wyśmiewać metodę za pomocą zakodowanego obiektu?

11

Pracuję nad aplikacją, która ma wiele warstw. Warstwa dostępu do danych do pobierania i zapisywania danych ze źródła danych, logika biznesowa do manipulacji danymi, interfejs użytkownika do wyświetlania danych na ekranie.

Robię również testy jednostkowe warstwy logiki biznesowej. Jedynym wymaganiem jest przetestowanie przepływu logiki warstwy biznesowej. Więc używam frameworku Moq, aby wyśmiewać warstwę dostępu do danych i testować jednostkę logiki biznesowej za pomocą MS Unit.

Korzystam z programowania interfejsów, aby rozdzielić projekt w jak największym stopniu, aby można było przeprowadzić test jednostkowy. Warstwa biznesowa warstwa dostępu do danych za pośrednictwem interfejsu.

Mam problem z próbą przetestowania jednej z metod logiki biznesowej. Ta metoda działa trochę, tworzy obiekt i przekazuje go do warstwy dostępu do danych. Kiedy próbuję kpić z tej metody warstwy dostępu do danych, nie może ona pomyślnie kpić.

Tutaj próbuję utworzyć kod demonstracyjny, aby pokazać mój problem.

Model:

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

Warstwa dostępu do danych:

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);
    }
}

Warstwa logiki biznesowej:

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 jednostkowy:

[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);
    }

W przypadku testu jednostkowego w momencie drwiącym przesyłam obiekt pracownika, ale podczas wywoływania metody logiki biznesowej tworzy on inny obiekt pracownika wewnątrz metody. Dlatego nie mogę kpić z obiektu.

W takim razie jak zaprojektować, aby rozwiązać problem?

DeveloperArnab
źródło
Zwykle sztuką jest zawinięcie obiektu w interfejs i sprawienie, aby wszyscy jego użytkownicy używali tego interfejsu, następnie po prostu kpisz z interfejsu, alternatywnie możesz uczynić metodę wirtualną, a następnie moq może kpić z metody bez interfejsu. Jednak w tym przypadku nie ma pewności co do nosorożców lub innych.
Jimmy Hoffa

Odpowiedzi:

12

Zamiast tworzyć Employeeobiekt bezpośrednio przy użyciu new, twoja klasa Bllmoże użyć EmployeeFactorydo tego klasy, za pomocą metody createInstance, która jest wstrzykiwana przez konstruktor:

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

Konstruktor powinien poprowadzić obiekt fabryki przez interfejs IEmployeeFactory, aby można było łatwo zastąpić „prawdziwą” fabrykę sztuczną fabryką.

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(); 
        // ...
    }
    //...
}

Fabryka próbna może dostarczyć testowi dowolny Employeeobiekt potrzebny do testu (na przykład createInstancezawsze może zwrócić ten sam obiekt):

 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;
       }
 }

Teraz użycie tej makiety w teście powinno załatwić sprawę.

Doktor Brown
źródło
Czy możesz podać jeden przykład kodu, abym mógł wizualizować twoją teorię?
DeveloperArnab,
@DeveloperArnab: zobacz moją edycję.
Doc Brown
Bardzo pomocny ...
DeveloperArnab
4

Potraktowałbym to jako pojedynczą jednostkę do przetestowania.

Dopóki kontrolujesz wszystkie dane wejściowe, z których Employeeobiekt jest tworzony, fakt, że jest on tworzony w testowanym obiekcie, nie powinien mieć znaczenia. Potrzebujesz tylko próbnej metody, aby zwrócić oczekiwany wynik, jeśli treść argumentu jest zgodna z oczekiwaniami.

Oczywiście oznacza to, że musisz podać niestandardową logikę dla metody próbnej. Zaawansowanej logiki często nie można przetestować za pomocą jedynie próbnych „for x return y”.

W rzeczywistości, należy nie sprawiają, że powrót inny obiekt w testach niż to będzie w produkcji, bo jeśli nie, to nie byłoby testowanie kodu, który powinien go utworzyć. Ale ten kod jest integralną częścią kodu produkcyjnego i dlatego powinien być objęty również przypadkiem testowym.

Jan Hudec
źródło
Tak, nie przejmuję się wejściami warstwy dostępu do danych, chcę tylko wyśmiewać ten obiekt i zwrócić dane zakodowane na stałe, aby móc przetestować logikę biznesową. Problem polega jednak na tym, że dwa różne obiekty pracownika nie mogą kpić z metody warstwy dostępu do danych.
DeveloperArnab,
@DeveloperArnab: Obiekty będą różne, ale będą miały znaną treść. Wszystko, co musisz zrobić, to zrobić próbne porównanie niestandardowe zamiast tożsamości obiektu.
Jan Hudec,
@DeveloperArnab: Jeśli wstrzykniesz inny Employeeobiekt do testów, nie przetestujesz kodu, który normalnie go tworzy. Więc nie powinieneś tego zmieniać.
Jan Hudec,
0

Niektóre narzędzia testujące zawodzą, zawsze musisz używać interfejsów, a wszystko musi być utworzone w sposób umożliwiający zamianę obiektu opartego na interfejsie na inny.

Istnieją jednak lepsze narzędzia - weź Microsoft Fakes (nazywało się Moles), które pozwalają zamienić dowolny obiekt, nawet statyczny i globalny. Zastępowanie obiektów wymaga bardziej niskiego poziomu, więc nie musisz wszędzie używać interfejsów, zachowując przy tym sposób pisania testów, do których jesteś przyzwyczajony.

gbjbaanb
źródło