Что такое Quick/Nimble и зачем они нужны?
Quick и Nimble — это две взаимодополняющие библиотеки для языка Swift, предназначенные для написания тестов в стиле Behavior-Driven Development (BDD). BDD — это методология разработки, которая фокусируется на поведении приложения с точки зрения пользователя, а не на технических деталях реализации. Вместо сухих утверждений типа XCTAssertEqual(a, b) вы описываете, что должно произойти: "когда пользователь нажимает кнопку, приложение должно показать приветствие".
Quick — это фреймворк для описания тестовых сценариев. Он предоставляет DSL (Domain-Specific Language), который позволяет структурировать тесты в виде иерархии: describe (описание контекста), context (уточнение условий) и it (конкретный тест). Это делает тесты читаемыми и понятными даже для нетехнических участников команды (менеджеров, аналитиков).
Nimble — это библиотека для написания матчеров (assertions). Вместо стандартных XCTAssert вы используете выразительные конструкции: expect(value).to(equal(expected)), expect(array).to(contain(element)). Nimble предоставляет десятки встроенных матчеров и позволяет легко создавать свои собственные.
Зачем это нужно?
- Читаемость: Тесты на BDD выглядят как спецификация. Вы можете показать их заказчику и убедиться, что требования поняты правильно.
- Структурированность: Иерархия
describe/context/itпомогает организовать тесты логически, избегая "спагетти-кода". - Выразительные матчеры: Nimble делает утверждения более наглядными и информативными. Ошибки в тестах становятся понятными сразу.
- Удобство отладки: При падении теста вы видите не просто "ожидалось 5, получено 3", а полный контекст: "Когда пользователь вводит неверный пароль, система должна показать ошибку".
Установка Quick/Nimble
Quick и Nimble устанавливаются через CocoaPods или Swift Package Manager (SPM). Мы рассмотрим оба способа.
Установка через CocoaPods
Если вы еще не используете CocoaPods, установите его:
sudo gem install cocoapods
В корне вашего проекта создайте файл Podfile и добавьте в него:
target 'YourAppName' do
# Обычные зависимости (если есть)
use_frameworks!
target 'YourAppNameTests' do
inherit! :search_paths
pod 'Quick'
pod 'Nimble'
end
end
Затем выполните:
pod install
После этого откройте .xcworkspace файл.
Установка через Swift Package Manager (SPM)
В Xcode: File → Add Packages... и введите URL репозитория:
https://github.com/Quick/Quick.git
https://github.com/Quick/Nimble.git
Выберите последнюю стабильную версию и добавьте пакеты к вашей тестовой цели (YourAppNameTests).
Быстрый старт: первый тест
Создадим простой класс Calculator и протестируем его.
Класс для тестирования
// Calculator.swift
import Foundation
class Calculator {
func add(_ a: Int, _ b: Int) -> Int {
return a + b
}
func subtract(_ a: Int, _ b: Int) -> Int {
return a - b
}
func multiply(_ a: Int, _ b: Int) -> Int {
return a * b
}
}
Тест с использованием Quick и Nimble
// CalculatorSpec.swift
import Quick
import Nimble
@testable import YourAppName // Замените на имя вашего приложения
class CalculatorSpec: QuickSpec {
override func spec() {
describe("Calculator") {
var calculator: Calculator!
beforeEach {
calculator = Calculator()
}
context("when adding two numbers") {
it("should return the correct sum") {
let result = calculator.add(3, 5)
expect(result).to(equal(8))
}
}
context("when subtracting two numbers") {
it("should return the correct difference") {
let result = calculator.subtract(10, 4)
expect(result).to(equal(6))
}
}
context("when multiplying two numbers") {
it("should return the correct product") {
let result = calculator.multiply(4, 5)
expect(result).to(equal(20))
}
}
}
}
}
Запустите тесты (Cmd+U). Вы увидите зеленые галочки, если все прошло успешно.
Основные методы и функции Quick
1. describe
Что делает: Создает группу тестов, описывающую определенный класс, модуль или функциональность. Это корневой элемент иерархии.
Параметры: _ description: String — описание группы; closure: () -> Void — замыкание с тестами.
Пример:
describe("UserManager") {
// Тесты для UserManager
}
2. context
Что делает: Уточняет состояние или условие, в котором выполняются тесты. Позволяет описать сценарии: "когда пользователь авторизован", "когда данных нет".
Параметры: _ description: String — описание контекста; closure: () -> Void — замыкание.
Пример:
context("when user is logged in") {
it("should show profile") { ... }
}
3. it
Что делает: Определяет конкретный тест. Внутри пишутся утверждения (expectations).
Параметры: _ description: String — описание теста; closure: () throws -> Void — замыкание с тестом.
Пример:
it("should return the correct sum") {
expect(calculator.add(2, 2)).to(equal(4))
}
4. beforeEach / afterEach
Что делает: Выполняют код перед/после каждым тестом в текущей группе. Используются для инициализации и очистки состояния.
Параметры: closure: () throws -> Void.
Пример:
beforeEach {
calculator = Calculator()
}
afterEach {
calculator = nil
}
5. beforeSuite / afterSuite
Что делает: Выполняются один раз до/после всех тестов в файле. Полезны для настройки глобальных ресурсов (база данных, моки).
Параметры: closure: () throws -> Void.
Пример:
beforeSuite {
setupDatabase()
}
afterSuite {
tearDownDatabase()
}
6. pending
Что делает: Помечает тест как "ожидающий". Такой тест не выполняется, но отображается в отчете. Удобно для планирования.
Параметры: _ description: String; closure: () throws -> Void (необязательно).
Пример:
pending("should handle network errors") {
// TODO: написать тест
}
7. xit
Что делает: Временно отключает тест. Аналогично pending, но более явно.
Параметры: Те же, что у it.
Пример:
xit("should handle edge case") {
// Этот тест не запустится
}
Основные методы и функции Nimble
8. expect(...).to(equal(...))
Что делает: Проверяет равенство значений. Самый базовый матчер.
Параметры: expect(_ expression: T) — выражение; .to(equal(_ expected: T)) — ожидаемое значение. T должен соответствовать Equatable.
Пример:
expect(result).to(equal(42))
expect(name).to(equal("Alice"))
9. expect(...).to(beTrue()) / beFalse()
Что делает: Проверяет, что выражение является true или false.
Параметры: Нет.
Пример:
expect(isValid).to(beTrue())
expect(hasError).to(beFalse())
10. expect(...).to(beNil()) / beNil()
Что делает: Проверяет, что значение равно nil или не равно nil.
Параметры: Нет.
Пример:
expect(optionalValue).to(beNil())
expect(nonOptionalValue).toNot(beNil())
11. expect(...).to(contain(...))
Что делает: Проверяет, что коллекция (массив, строка) содержит элемент.
Параметры: contain(_ elements: T...) — элементы для поиска.
Пример:
expect([1, 2, 3]).to(contain(2))
expect("Hello").to(contain("ell"))
12. expect(...).to(beCloseTo(...))
Что делает: Проверяет, что число близко к ожидаемому с заданной точностью. Полезно для чисел с плавающей точкой.
Параметры: beCloseTo(_ expected: Double, within delta: Double = 0.001).
Пример:
expect(0.1 + 0.2).to(beCloseTo(0.3, within: 0.0001))
13. expect(...).to(throwError())
Что делает: Проверяет, что замыкание выбрасывает ошибку. Можно уточнить тип ошибки.
Параметры: throwError(_ error: Error? = nil) — опциональная конкретная ошибка.
Пример:
expect {
try divide(5, 0)
}.to(throwError(DivisionError.divisionByZero))
14. expect(...).to(satisfyAnyOf(...))
Что делает: Проверяет, что выражение удовлетворяет хотя бы одному из переданных матчеров. Аналог логического "ИЛИ".
Параметры: satisfyAnyOf(_ matchers: Matcher.
Пример:
expect(value).to(satisfyAnyOf(equal(1), equal(2)))
15. expect(...).to(allPass(...))
Что делает