Я понимаю, что этот вопрос может показаться дубликатом таких вопросов, как это , это , это , это , и это . Однако я специально спрашиваю, как бы вы написали модульные тесты, используя стиль Детройта, к нетривиальному коду с несколькими путями кода. Другие вопросы, статьи и объяснения обсуждают тривиальные примеры, такие как класс Calculator
. Кроме того, я практикую CQS, или разделение командных запросов, которое меняет методы, с помощью которых я пишу тесты.
Согласно статье Мартина Фаулера " Насмешки не являются заглушками ", я понимаю что к TDD существуют две школы мысли - классическая (Детройт) и мокистская (Лондон).
Когда я впервые изучил модульное тестирование и TDD в целом, меня учили лондонскому стилю, используя Mocking Frameworks, как Java Mockito. Я понятия не имел о существовании Classical TDD.
. Чрезмерное использование Mocks в лондонском стиле беспокоит меня тем, что тесты очень сильно привязаны к реализации, делая их хрупкими. Учитывая, что многие написанные мною тесты носили поведенческий характер с использованием имитаций, я хотел бы узнать и понять, как вы будете писать тесты в классическом стиле.
Для этого у меня есть несколько вопросов. Для классического тестирования:
- Должны ли вы использовать реальную реализацию заданной зависимости или фальшивого класса?
- Есть ли у практикующих в Детройте иное определение понятия "юнит", чем у Мокистов? do?
Для более подробной информации приведу пример нетривиального реального кода для регистрации пользователя в REST API.
public async signUpUser(userDTO: CreateUserDTO): Promise<void> {
const validationResult = this.dataValidator.validate(UserValidators.createUser, userDTO);
if (validationResult.isLeft())
return Promise.reject(CommonErrors.ValidationError.create('User', validationResult.value));
const [usernameTaken, emailTaken] = await Promise.all([
this.userRepository.existsByUsername(userDTO.username),
this.userRepository.existsByEmail(userDTO.email)
]) as [boolean, boolean];
if (usernameTaken)
return Promise.reject(CreateUserErrors.UsernameTakenError.create());
if (emailTaken)
return Promise.reject(CreateUserErrors.EmailTakenError.create());
const hash = await this.authService.hashPassword(userDTO.password);
const user: User = { id: 'create-an-id', ...userDTO, password: hash };
await this.userRepository.addUser(user);
this.emitter.emit('user-signed-up', user);
}
С моим знанием Подход mocking, я бы обычно высмеивал каждую отдельную зависимость здесь, чтобы mocks отвечал с определенными результатами для заданных аргументов, а затем утверждал, что метод хранилища addUser
был вызван с правильным пользователем.
Используя классический подход для тестирования у меня будет FakeUserRepository
, который работает с коллекцией в памяти и делает утверждения о состоянии репозитория. Проблема в том, что я не уверен, как вписываются dataValidator
и authService
. Должны ли они быть реальными реализациями, которые фактически проверяют данные и на самом деле имеют sh пароли? Или они тоже должны быть подделками, которые уважают свои соответствующие интерфейсы и возвращают запрограммированные ответы на определенные входные данные?
В других методах Service есть обработчик исключений, который генерирует определенные исключения на основе исключений, выданных из authService
. Как вы проводите государственное тестирование в этом случае? Вам нужно создать фейк, который соблюдает интерфейс и генерирует исключения на основе определенных входных данных? Если да, разве мы не вернулись к созданию макетов сейчас?
Чтобы дать вам другой пример функции, для которой я не был бы уверен, как построить фальшивки, посмотрите этот метод декодирования токена JWT, который часть моего AuthenticationService
:
public verifyAndDecodeAuthToken(
candidateToken: string,
opts?: ITokenDecodingOptions
): Either<AuthorizationErrors.AuthorizationError, ITokenPayload> {
try {
return right(
this.tokenHandler.verifyAndDecodeToken(candidateToken, 'my-secret', opts) as ITokenPayload
);
} catch (e) {
switch (true) {
case e instanceof TokenErrors.CouldNotDecodeTokenError:
throw ApplicationErrors.UnexpectedError.create();
case e instanceof TokenErrors.TokenExpiredError:
return left(AuthorizationErrors.AuthorizationError.create());
default:
throw ApplicationErrors.UnexpectedError.create();
}
}
}
Здесь вы можете видеть, что функция может выдавать разные ошибки, которые будут иметь разные значения для вызывающей стороны API. Если бы я создавал фальшивку здесь, единственное, что я мог подумать, это заставить фальшивку отвечать определенными ошибками на жестко запрограммированные входы, но опять же, сейчас это похоже на пересмотр фреймворка.
* 1049 Так что, в конце концов, я не уверен, как вы пишете модульные тесты без mock-ов, используя классический метод утверждения на основе состояний, и я был бы признателен за любые советы о том, как это сделать для моего примера кода выше. Благодаря.