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