Проверка вызова некоторого метода должна принадлежать модульным тестам

#c# #asp.net #unit-testing

#c# #asp.net #модульное тестирование

Вопрос:

Я пытаюсь следовать тестированию поведения, а не метода (это то, что я изначально делаю). Например, я тестирую функциональность, в которой я обновляю элементы с сервера.

В основном это делается в методе RefreshItems() . В этом методе у меня есть несколько вызовов других методов другого сервиса, например, вы можете сказать NetworkService (для веб-запроса).

Поскольку реализация RefreshItems может быть изменена или может не вызывать какой-либо конкретный метод NetworkService.

Так должны ли мои UnitTests включать тест для проверки того, были ли вызваны методы NetworkService или нет?

Ответ №1:

Введите интерфейс для NetworkService . Он может быть вызван INetworkService , но IItemsService может использоваться:

 public interface IItemsService
{
    Items GetAllItems(); //or whatever you need
}
 

Тогда у вас есть два варианта:

Макет и проверка

Введите макет для этого интерфейса и убедитесь, что вызываются правильные вызовы, но, как вы говорите, это проверит реализацию, а не поведение.

 var itemsService = new Mock<IItemsService>(); //Moq for example
var testObject = new ClassUnderTest(itemsService.Object);
testObject.RefreshItems();
itemsService.Verify(e => e.GetAllItems(), Times.Once); //verify the calls to the moq
 

Заглушка

Создайте реализацию-заглушку IItemsService , которая возвращает фиксированные данные. Затем Assert новое состояние вашего тестируемого класса.

 var itemsService = new StubItemsService();
var testObject = new ClassUnderTest(itemsService);
testObject.RefreshItems();
Assert.AreEqual(1, testObject.Items); //some assert on the testObject
 

Обратите внимание, что вы также можете использовать Moq для заглушки, а не создавать класс заглушки:

 var itemsService = new Mock<IItemsService>();
itemsService.Setup(e => e.GetItems()).Returns(/*whatever*/);
var testObject = new ClassUnderTest(itemsService.Object);
testObject.RefreshItems();
Assert.AreEqual(1, testObject.Items); //some assert on the testObject
 

Комментарии:

1. мой вопрос в том, должны ли мои UnitTests включать отдельный тест для проверки, были ли вызваны методы NetworkService или нет?

2. Вы спрашиваете, потому что это сетевая служба. т.Е. Если бы это был просто другой класс, вы бы спросили об этом?

3. Нет, это может быть любой класс или какой-то другой метод.

4. мой вопрос связан с подходом UnitTesting. я могу писать модульные тесты.

5. Хватит болтать, покажите код 😊 Но так ли это, что внутри Refresh() вы вызываете несколько методов нескольких служб? Если да, то попробуйте поместить логику внутри зависимых служб и вне метода Refresh(). А при тестировании служб assert вызывает там метод assert, если это действительно необходимо.

Ответ №2:

Я бы проверил, что все необходимые вызовы были выполнены. При тестировании RefreshItems имитируйте зависимые службы (например, NetworkService) и проверяйте, были ли выполнены вызовы.

Редактировать:

Допустим, у вас есть интерфейс и реализация MyService, подобные этому.

 public interface IMyService
{
    IEnumerable<string> Get();
    void SuperImportantMethod();
}

public class MyService : IMyService
{
    public IEnumerable<string> Get()
    {
        SuperImportantMethod();
        return new string[] { string.Empty };
    }

    public virtual void SuperImportantMethod()
    {
        // and secret!
    }
}
 

А затем MyClass, который использует эту службу, как:

 public class MyClass
{
    public MyClass()
    {}

    public MyClass(IMyService service)
    {
        Service = service;
    }
    public void RefreshItems()
    {
        Items = Service.Get();
    }

    public IMyService Service { get; set; }

    public IEnumerable<string> Items { get; set; }
}
 

Затем вы можете протестировать RefreshItems метод MyClass, изолированный от службы.

 [TestMethod]
public void RefreshItemsTest()
{
    var expected = new string[] {"my", "items", "here"};

    var myClass = new MyClass();
    var mock = new Mock<IMyService>();

    mock.Setup(service => service.Get()).Returns(expected);
    myClass.Service = mock.Object;

    myClass.RefreshItems();
    Assert.IsTrue(expected.SequenceEqual(myClass.Items));
}
 

И при тестировании MyService вы можете убедиться, что SuperImportantMethod он вызывается каждый раз, когда сервисы «получают» что-то.

 [TestClass]
public class MyServiceTests
{
    [TestMethod]
    public void GetTest()
    {
        var mock = new Mock<MyService>();
        var service = mock.Object;

        service.Get();
        mock.Verify(s => s.SuperImportantMethod(), Times.Once);
    }
}
 

Комментарии:

1. Модульный тест не должен быть таким — мы должны проверять поведение, а не реализацию. Может случиться так, что нам придется изменить реализацию RefreItems, тогда в этом случае все сломается. у нас будет тест на вызов некоторого метода, который в конечном итоге не существует в новой реализации.

2. Если вы можете изолировать RefreshItems() и вам действительно не нужно знать, вызывал ли NetworkService какой-либо метод, то, конечно, вы бы его не использовали. Я пересматриваю свой ответ с примером.