Страницы

Поиск по вопросам

среда, 22 января 2020 г.

Выражение Assert при тестировании метода, который обновляет объект через сторонний сервис

#c_sharp #юнит_тесты #test_driven_development #mocking #nsubstitute


Код

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

public class Customer
{
    public string Surname { get; set; }
    public string Name { get; set; }
    public Company Company { get; set; }
}

public class CustomerDto
{        
    public int Id { get; set; }

    [Required]
    public string Surname { get; set; }

    [Required]
    public string Name { get; set; }

    public string SecondName { get; set; }
}

public class CustomerService
{
    private readonly IGenericRepository _customerRepository;

    public CustomerService(IGenericRepository customerRepository)
    {
        _customerRepository = customerRepository;
    }

    public CustomerDto Add(CustomerDto dto)
    {
        //Validation
        if (dto == null)
        {
            throw new ArgumentNullException(nameof(dto));
        }
        var validContext = new System.ComponentModel.DataAnnotations.ValidationContext(dto);
        Validator.ValidateObject(dto, validContext);

        //Mapping by AutoMapper
        Mapper.Initialize(cfg => cfg.CreateMap());
        var entity = Mapper.Map(dto);            

        _customerRepository.Insert(entity);
        _customerRepository.Save();

        dto.Id = entity.Id;

        return dto;
    }
}




Юнит-тестирование CustomerService.Add()

Необходимо создать юнит-тест для метода Add(). Изначально, вообще была попытка реализовать
этот метод через TDD, т.е. сначала создать для него юнит-тест и только затем имлемантацию.
Но я не смог этого сделать так как не разобрался за что конкретно отвечает этот метод
и надо ли вообще писать для него тест.

Но два юнит-теста я сделал и вот как все это выглядит:

[TestClass]
public class Customer_Test
{
    [TestMethod]
    public void Add_Test()
    {
        //Arrange
        //Use NSubstitude for mocking
        var mockRepository = Substitute.For>();

        //Act
        var service = new CustomerService(mockRepository);
        var dto = new CustomerDto
        {
            Surname = "Shakhabov",
            Name = "Adam"
        };
        var addedDto = service.Add(dto);

        //Assert
        Assert.IsNotNull(addedDto);
    }

    [TestMethod]
    [ExpectedException(typeof(ValidationException))]
    public void Add_TestException()
    {
        //Arrange
        var mockRepository = Substitute.For>();

        //Act
        var service = new CustomerService(mockRepository);
        var dto = new CustomerDto
        {
            Surname = "Shakhabov",                
        };
        var addedDto = service.Add(dto);

        //Assert
        //expectedException ValidationException
    }
}


Вот что (цель) должен сделать метод: Добавить в базу данных объект на основе полученного
DTO-объекта и затем обратно вернуть этот DTO-объект, но уже с обновленным полем Id,
значение для которого сформировала СУБД. 

Вот как (задачи) делает это метод:


Получив DTO-объект, сразу верифицирует его;
Используя библиотеку AutoMapper, проецирует DTO на сущность, которая в свою очередь
создается с нуля;
Используя объект-репозиторий, добавляет сущность в контекст.
Сохраняет контекст в результате чего источник данных (СУБД)
инициализирует поле Id у сущности (entity);
Инициализирует поле dto.Id полем entity.Id и возвращает DTO.




Вопрос

Правильно ли были сформулированы требования для метода Add() и соответственно построены
юнит-тесты на их основе? Какие тесты (выражения Assert) создаются для методов с подобной
логикой, где целью является инициализировать свойство DTO-объекта значением, получаемое
сторонним сервисом (в данном случаи объект-репозиторий)?
    


Ответы

Ответ 1



Смотрим в метод Add. Что делает метод добавления? Добавляет новую сущность в БД и присваивает ей уникальный идентификатор. С точки зрения возвращаемого значения - нужно вернуть только идентификатор, так как весь объект остался без изменений. Когда DTO приходит в метод добавления - идентификатор нулевой, то есть не заполнен. Когда сущьность (entity) будет добавлена в БД, ей будет присвоен новый уникальный идентифкатор. Так как используется тип данных int, то иднетификатор обязательно будет положительным (больше нуля) и уникален. Конвертацию из DTO в объект сущности БД перенести внутрь DTO или добавить конструктор в сущность БД, который примет в качестве параметра DTO-шку. Про конвертацию подробнее: либо в объекте Customer сделать конструктор, который будет принимать объект типа CustomerDto, где внутри произвести присвоение полей с помощью AutoMapper, либо в CustomerDto сделать метод ToCustomer(), внутри которого создастся объект Customer и будут присвоены поля, который вернет объект типа Customer. Думаю, что с точки зрения логики, метод Add не должен делать эту конвертацию, так как его задача взять объект, добавить его и на основании результата уже будем знать, прошло ли все успешно. Пример кода метода ToCustomer() в классе CustomerDto, для этого возможно понадобится подключить using для типа Customer и для AutoMapper, но это не беда, вот что я имел в виду: public Customer ToCustomer() { //Mapping by AutoMapper Mapper.Initialize(cfg => cfg.CreateMap()); var entity = Mapper.Map(dto); return entity; } По-хорошему и валидацию сделать бы более компактной и красивой, но это уже на ваше усмотрение. Еще один совет, инкапсулируйте в репозитрорий методы Insert и Save внутрь одного метода, который выполнит это (назовите его Create или Add). Ну и навесьте try...catch внутри метода, к примеру, если что-то пошло не так и упало исключение - тогда метод вернул -1, а в тестах вы уже будете знать, что если меньше нуля - то тест не прошел. Касаемо тестов, у вас два сценария: метод отработал успешно и без исключения - нужно проверить, что идентификатор не нулевой (больше нуля); пробуем подсунуть некорректные данные, которые вызовут исключение. Соответственно, тестовый метод, проверяющий успешную работу должен проверить, что идентификатор не нулевой. Пример проверки: C# unit test, how to test greater than Там где ожидается исключение - не должно быть Assert проверок. Касаемо TDD - это сложная практика, которая нарабатывается с опытом. Проще начать с написания простых классов через TDD, а потом уже переходить к более сложным сценариям. Чтобы писать через TDD, нужно иметь четкое представление того, как должна работать та или иная функциональность, которую вы хотите реализовать с ее помощью. А еще, сейчас я занимаюсь приведением большого проекта в порядок с помощью одного вспомогательного компонента под названием StyleCop, так вот он не рекомендует использовать знак нижнего подчеркивания в именовании свойств, полей, параметров, переменных и прочего. Ну это так, к слову. Надеюсь, что мои советы и пояснения достаточно ясны и не вызовут противоречий.

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

Отправить комментарий