Код
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
public CustomerService(IGenericRepository
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
_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
По-хорошему и валидацию сделать бы более компактной и красивой, но это уже на ваше усмотрение. Еще один совет, инкапсулируйте в репозитрорий методы Insert и Save внутрь одного метода, который выполнит это (назовите его Create или Add). Ну и навесьте try...catch внутри метода, к примеру, если что-то пошло не так и упало исключение - тогда метод вернул -1, а в тестах вы уже будете знать, что если меньше нуля - то тест не прошел.
Касаемо тестов, у вас два сценария:
метод отработал успешно и без исключения - нужно проверить, что идентификатор не нулевой (больше нуля);
пробуем подсунуть некорректные данные, которые вызовут исключение.
Соответственно, тестовый метод, проверяющий успешную работу должен проверить, что идентификатор не нулевой. Пример проверки: C# unit test, how to test greater than Там где ожидается исключение - не должно быть Assert проверок.
Касаемо TDD - это сложная практика, которая нарабатывается с опытом. Проще начать с написания простых классов через TDD, а потом уже переходить к более сложным сценариям. Чтобы писать через TDD, нужно иметь четкое представление того, как должна работать та или иная функциональность, которую вы хотите реализовать с ее помощью.
А еще, сейчас я занимаюсь приведением большого проекта в порядок с помощью одного вспомогательного компонента под названием StyleCop, так вот он не рекомендует использовать знак нижнего подчеркивания в именовании свойств, полей, параметров, переменных и прочего. Ну это так, к слову. Надеюсь, что мои советы и пояснения достаточно ясны и не вызовут противоречий.
Комментариев нет:
Отправить комментарий