Як знущатися з методом з жорстко закодованим об'єктом?


11

Я працюю над додатком, який має кілька шарів. Шар доступу до даних для отримання та збереження даних з джерела даних, бізнес-логіка для маніпулювання даними, інтерфейс користувача для відображення даних на екрані.

Я також роблю одиничне тестування рівня бізнес-логіки. Єдина вимога - перевірити потік логіки бізнес-шару. Тому я використовую Moq Framework для знущання над рівнем доступу до даних і тестуванням рівня бізнес-логіки за допомогою MS Unit.

Я використовую програмування інтерфейсу, щоб зробити дизайн деакульованим максимально, щоб можна було зробити тест одиниці. Рівень доступу до викликів бізнес-рівня через інтерфейс

Я зіткнувся з проблемою, коли я намагаюся перевірити один із методів ділової логіки. Цей метод виконує певну роботу і створює об'єкт і передає його до рівня доступу до даних. Коли я намагаюся знущатися над методом рівня доступу до даних, він не може успішно знущатися.

Тут я намагаюся створити демо-код, щоб показати свою проблему.

Модель:

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

Рівень доступу до даних:

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

Логічний рівень бізнесу:

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

Тест одиниці:

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

У тестовому випадку підрозділу під час глузування я надсилаю об’єкт Employee, але коли визываю метод бізнес-логіки, він створює інший об'єкт Employee всередині методу. Ось чому я не можу знущатися над об’єктом.

У такому випадку як спроектувати так, щоб я міг вирішити проблему?


Зазвичай хитрість полягає в тому, щоб обернути об'єкт в інтерфейс і змусити всіх споживачів його використовувати цей інтерфейс, тоді ви просто знущаєтесь над інтерфейсом, або ви можете зробити метод віртуальним, а потім moq може знущатися над методом без інтерфейсу. Однак не відомо про носороги та інші в цьому випадку.
Джиммі Хоффа

Відповіді:


12

Замість того, щоб створювати Employeeоб'єкт безпосередньо за допомогою new, ваш клас Bllможе використовувати EmployeeFactoryдля цього клас із методом createInstance, який вводиться через конструктор:

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

Конструктор повинен прийняти заводський об’єкт через інтерфейс IEmployeeFactory, так що ви можете легко замінити "справжню" фабрику на фабрику макетів.

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

Фабрика макетів може забезпечити тест будь-яким Employeeоб'єктом, який вам потрібен для вашого тесту (наприклад, createInstanceзавжди може повернути той самий об’єкт):

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

Тепер, використовуючи цей макет у своєму тесті, слід зробити трюк.


Чи можете ви надати мені один приклад коду, щоб я міг уявити вашу теорію?
DeveloperArnab

@DeveloperArnab: див. Мою редакцію.
Док Браун

Дуже корисно ...
DeveloperArnab

4

Я б вважав це єдиним блоком для тестування.

Поки ви керуєте всіма входами, з яких створений Employeeоб’єкт, факт його створення в тестованому об'єкті не повинен мати значення. Вам просто потрібен метод макету, щоб повернути очікуваний результат, якщо зміст аргументу відповідає очікуванню.

Очевидно, це означає, що вам потрібно надати власну логіку для методу макету. Розширена логіка часто не може бути перевірена лише макетами "for x return y".

Насправді, ви не повинні змушувати його повертати в тестах інший об’єкт, ніж це буде у виробництві, тому що якби ви це не зробили, ви б не тестували код, який повинен його створити. Але цей код є невід'ємною частиною виробничого коду, тому його слід охоплювати і тестовим кодом.


Так, я не турбуюся про входи рівня шару доступу до даних, я хочу лише знущатися над цим об'єктом і повертати жорстко закодовані дані, щоб я міг перевірити ділову логіку. Але проблема полягає в тому, що з двох різних об'єктів Employee я не можу глузувати з методу рівня доступу до даних.
DeveloperArnab

@DeveloperArnab: Об'єкти будуть різними, але вони матимуть відомий вміст. Отже, все, що вам потрібно зробити, - це зробити макет робити порівняння на замовлення замість ідентичності об'єкта.
Ян Худек

@DeveloperArnab: Якщо ви вводите інший Employeeоб'єкт у тести, ви не перевірите код, який його зазвичай створює. Тож не слід це змінювати.
Ян Худек

0

У разі відмови деяких інструментів тестування, ви завжди повинні використовувати інтерфейси, і все повинно бути створено таким чином, що ви можете замінити об'єкт на основі інтерфейсу на інший.

Однак є кращі інструменти - візьміть Microsoft Fakes (називався Moles), який дозволяє поміняти місцями будь-які об’єкти, навіть статичні та глобальні. Для заміни об'єктів потрібен більш низький рівень підходу, тому вам не доведеться використовувати інтерфейси скрізь, але все ще зберігаєте спосіб написання тестів, до якого ви звикли.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.