В чём разница между моками и стабами?
Комментарии (3)
Ответ сгенерирован нейросетью и может содержать ошибки
Разница между моками и стабами в iOS-разработке
В контексте модульного тестирования (unit testing) в iOS, моки (mocks) и стабы (stubs) — это два типа тестовых дублёров (test doubles), которые служат разным целям, хотя часто используются вместе. Их ключевое различие заключается в назначении и способе проверки в тестах.
Основные определения
Стабы (Stubs) — это объекты, которые предоставляют предопределённые ответы на вызовы методов во время теста. Их главная задача — заменить реальные зависимости, чтобы изолировать тестируемый код и контролировать его поведение в различных сценариях (например, успех, ошибка, пустые данные).
Моки (Mocks) — это объекты, которые регистрируют вызовы своих методов и позволяют проверить, как тестируемый код взаимодействовал с ними. Их основная цель — верификация поведения (behavior verification), то есть проверка, что определённые методы были вызваны с правильными параметрами, в нужном порядке и нужное количество раз.
Ключевые различия
| Аспект | Стабы (Stubs) | Моки (Mocks) |
|---|---|---|
| Основная цель | Контроль состояния (state) — предоставление данных | Проверка взаимодействия (interaction) — верификация вызовов |
| Проверка в тестах | Проверяют результат работы тестируемого кода | Проверяют процесс — как вызывались методы зависимостей |
| Типичные сценарии | Возврат успешных/ошибочных данных, симуляция состояний | Проверка отправки сетевых запросов, вызовов делегатов, логирования |
| Гибкость | Часто простые, с жёстко заданными ответами | Могут быть сложными, с отслеживанием истории вызовов |
Примеры в Swift с XCTest
Пример стаба
// Протокол зависимости
protocol NetworkService {
func fetchUser(completion: @escaping (Result<User, Error>) -> Void)
}
// Стаб для успешного ответа
class NetworkServiceStub: NetworkService {
var predefinedUser: User
init(user: User) {
self.predefinedUser = user
}
func fetchUser(completion: @escaping (Result<User, Error>) -> Void) {
completion(.success(predefinedUser)) // Предопределённый ответ
}
}
// В тесте
func testUserProfileLoadedSuccessfully() {
let stubUser = User(name: "Иван", age: 30)
let stubService = NetworkServiceStub(user: stubUser)
let viewModel = UserViewModel(service: stubService)
viewModel.loadUser()
// Проверяем РЕЗУЛЬТАТ — состояние viewModel
XCTAssertEqual(viewModel.userName, "Иван")
XCTAssertEqual(viewModel.userAge, 30)
}
Пример мока
// Мок для проверки взаимодействий
class NetworkServiceMock: NetworkService {
var fetchUserCallCount = 0
var lastCompletion: ((Result<User, Error>) -> Void)?
func fetchUser(completion: @escaping (Result<User, Error>) -> Void) {
fetchUserCallCount += 1 // Регистрируем вызов
lastCompletion = completion // Сохраняем для последующего управления
}
}
// В тесте
func testFetchUserCalledExactlyOnce() {
let mockService = NetworkServiceMock()
let viewModel = UserViewModel(service: mockService)
viewModel.loadUser()
// Проверяем ВЗАИМОДЕЙСТВИЕ — как вызывался метод
XCTAssertEqual(mockService.fetchUserCallCount, 1) // Был вызван ровно один раз
}
Практическое применение в iOS
-
Стабы чаще используют для:
- Тестирования обработки различных ответов сервера (успех, ошибка 404, 500)
- Симуляции состояний системы (нет сети, включён/выключен GPS)
- Замены сложных или медленных зависимостей (база данных, файловая система)
-
Моки чаще используют для:
- Проверки отправки аналитических событий или логов
- Верификации вызовов делегатов или колбэков
- Тестирования, что определённые методы были вызваны в правильной последовательности
- Проверки взаимодействия с системными фреймворками (UserNotifications, CoreLocation)
Современные подходы
В современных iOS-проектах часто используют комбинированные подходы:
- Умные моки (smart mocks), которые могут и возвращать предопределённые значения (как стабы), и верифицировать вызовы
- Фреймворки для мокинга вроде Cuckoo или Mockingbird, которые генерируют моки автоматически
- Протокол-ориентированный дизайн, который облегчает создание и тестовых дублёров через протоколы
Заключение
Основное философское различие: стабы помогают тестировать "что" (какой результат получился), а моки — "как" (каким образом этот результат был достигнут). В практике iOS-разработки оба подхода дополняют друг друга: стабы изолируют код от внешних зависимостей, а моки проверяют корректность взаимодействия с этими зависимостями. Эффективное использование обоих типов тестовых дублёров приводит к созданию более надёжных, поддерживаемых и тестируемых приложений.