Страницы

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

вторник, 12 марта 2019 г.

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

Код
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-объекта значением, получаемое сторонним сервисом (в данном случаи объект-репозиторий)?


Ответ

Смотрим в метод 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, так вот он не рекомендует использовать знак нижнего подчеркивания в именовании свойств, полей, параметров, переменных и прочего. Ну это так, к слову. Надеюсь, что мои советы и пояснения достаточно ясны и не вызовут противоречий.

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

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