SlideShare a Scribd company logo
Александр Сычев
Как сделать так, чтобы тесты на
Swift не причиняли боль 🤕
Tests in Swift
Александр Сычев
a.sychev@rambler-co.ru
@asychev89
Tests in Swift
• Требования к тестам
• Что мы поменяли
• Swift-style тесты
• Советы и рекомендации
Tests in Swift
MAINTAINABILITY
Tests in Swift
Tests in Swift
https://twitter.com/noahsussman
Tests in Swift
Преимущества
• Понимание требований
• Стабильность при рефакторинге
• Документация
• Хороший API
• Меньше ошибок
Tests in Swift
Преимущества
• Понимание требований
• Стабильность при рефакторинге
• Документация
• Хороший API
• Меньше ошибок
Tests in Swift
Преимущества
• Понимание требований
• Стабильность при рефакторинге
• Документация
• Хороший API
• Меньше ошибок
Tests in Swift
Преимущества
• Понимание требований
• Стабильность при рефакторинге
• Документация
• Хороший API
• Меньше ошибок
Tests in Swift
Преимущества
• Понимание требований
• Стабильность при рефакторинге
• Документация
• Хороший API
• Меньше ошибок
Tests in Swift
Инструменты 🔨
Tests in Swift
xUnit
Tests in Swift
- (void)testThatObjectsWithEqualTitleAreEqual {
// given
RubricsObject *object1 = [[RubricsObject alloc] initWithTagID:@"tagID1"
title:@"Title"];
RubricsObject *object2 = [[RubricsObject alloc] initWithTagID:@"tagID2"
title:@"Title"];
// when
BOOL equal = [object1 isEqual:object2];
// then
XCTAssertTrue(equal);
}
Tests in Swift
- (void)testThatObjectsWithEqualTitleAreEqual {
// given
RubricsObject *object1 = [[RubricsObject alloc] initWithTagID:@"tagID1"
title:@"Title"];
RubricsObject *object2 = [[RubricsObject alloc] initWithTagID:@"tagID2"
title:@"Title"];
// when
BOOL equal = [object1 isEqual:object2];
// then
XCTAssertTrue(equal);
}
// given
// when
// then
Tests in Swift
- (void)testThatObjectsWithEqualTitleAreEqual {
// given
RubricsObject *object1 = [[RubricsObject alloc] initWithTagID:@"tagID1"
title:@"Title"];
RubricsObject *object2 = [[RubricsObject alloc] initWithTagID:@"tagID2"
title:@"Title"];
// when
BOOL equal = [object1 isEqual:object2];
// then
XCTAssertTrue(equal);
}
// given
// when
// then
Tests in Swift
- (void)testThatObjectsWithEqualTitleAreEqual {
// given
RubricsObject *object1 = [[RubricsObject alloc] initWithTagID:@"tagID1"
title:@"Title"];
RubricsObject *object2 = [[RubricsObject alloc] initWithTagID:@"tagID2"
title:@"Title"];
// when
BOOL equal = [object1 isEqual:object2];
// then
XCTAssertTrue(equal);
}
// given
// when
// then
Tests in Swift
- (void)testThatObjectsWithEqualTitleAreEqual {
// given
RubricsObject *object1 = [[RubricsObject alloc] initWithTagID:@"tagID1"
title:@"Title"];
RubricsObject *object2 = [[RubricsObject alloc] initWithTagID:@"tagID2"
title:@"Title"];
// when
BOOL equal = [object1 isEqual:object2];
// then
XCTAssertTrue(equal);
}
// given
// when
// then
Tests in Swift
What makes a clean test? Three things.
Readability, readability, and readability.
Robert C. Martin
Tests in Swift
• Предметно-ориентированный язык
• Нет лишнего контекста
• Проверка одной истины
Чистый тест
Tests in Swift
Предметно-ориентированный язык
- (void)test1 {
RubricsObject *obj = [RubricsObject new];
XCTAssertEqualObjects(obj1, obj2);
}
Tests in Swift
Предметно-ориентированный язык
- (void)test1 {
RubricsObject *obj = [RubricsObject new];
XCTAssertEqualObjects(obj1, obj2);
}
Tests in Swift
testThat<SystemUnderTest><ExpectedRequirement>
Tests in Swift
- (void)testThatObjectsWithEqualTitleAreEqual {
// given
RubricsObject *object1 = [[RubricsObject alloc] initWithTagID:@"tagID1"
title:@"Title"];
RubricsObject *object2 = [[RubricsObject alloc] initWithTagID:@"tagID2"
title:@"Title"];
// when
BOOL equal = [object1 isEqual:object2];
// then
XCTAssertTrue(equal);
}
Tests in Swift
Нет лишнего контекста
- (void)testThatMailBoxServiceObtainesCachedConnectedMailBoxConfigList {
// given
NSString *firstMailBoxNativeFolderName = @"firstFolder";
NSString *secondMailBoxNativeFolderName = @"secondFolder";
NSManagedObjectContext *managedObjectContext = [NSManagedObjectContext MR_rootSavingContext];
[managedObjectContext performBlockAndWait:^{
RCMMailBox *firstMailBox = [RCMMailBox MR_createEntityInContext:managedObjectContext];
firstMailBox.nativeFolder = firstMailBoxNativeFolderName;
RCMMailBox *secondMailBox = [RCMMailBox MR_createEntityInContext:managedObjectContext];
secondMailBox.nativeFolder = secondMailBoxNativeFolderName;
[managedObjectContext MR_saveOnlySelfAndWait];
}];
// when
NSArray *mailBoxList = [self.mailBoxService obtainCachedConnectedMailBoxConfigList];
// then
BOOL containsFirstMailBox;
BOOL containsSecondMailBox;
for (id<RCMMailBoxConfig> mailBoxConfig in mailBoxList) {
if ([mailBoxConfig.nativeFolder isEqualToString:firstMailBoxNativeFolderName]) {
containsFirstMailBox = YES;
}
else if ([mailBoxConfig.nativeFolder isEqualToString:secondMailBoxNativeFolderName]) {
containsSecondMailBox = YES;
}
}
XCTAssertTrue(containsFirstMailBox);
XCTAssertTrue(containsSecondMailBox);
}
Tests in Swift
Нет лишнего контекста
- (void)testThatMailBoxServiceObtainesCachedConnectedMailBoxConfigList {
// given
NSString *firstMailBoxNativeFolderName = @"firstFolder";
NSString *secondMailBoxNativeFolderName = @"secondFolder";
NSManagedObjectContext *managedObjectContext = [NSManagedObjectContext MR_rootSavingContext];
[managedObjectContext performBlockAndWait:^{
RCMMailBox *firstMailBox = [RCMMailBox MR_createEntityInContext:managedObjectContext];
firstMailBox.nativeFolder = firstMailBoxNativeFolderName;
RCMMailBox *secondMailBox = [RCMMailBox MR_createEntityInContext:managedObjectContext];
secondMailBox.nativeFolder = secondMailBoxNativeFolderName;
[managedObjectContext MR_saveOnlySelfAndWait];
}];
// when
NSArray *mailBoxList = [self.mailBoxService obtainCachedConnectedMailBoxConfigList];
// then
BOOL containsFirstMailBox;
BOOL containsSecondMailBox;
for (id<RCMMailBoxConfig> mailBoxConfig in mailBoxList) {
if ([mailBoxConfig.nativeFolder isEqualToString:firstMailBoxNativeFolderName]) {
containsFirstMailBox = YES;
}
else if ([mailBoxConfig.nativeFolder isEqualToString:secondMailBoxNativeFolderName]) {
containsSecondMailBox = YES;
}
}
XCTAssertTrue(containsFirstMailBox);
XCTAssertTrue(containsSecondMailBox);
}
😱
Tests in Swift
Extract Method
Tests in Swift
Проверка одной истины
assert one truth
Tests in Swift
Требования
• Скорость
• Надежность
Tests in Swift
Требования
• Скорость
• Надежность 🚀
Tests in Swift
Требования
• Скорость
• Надежность
Tests in Swift
Библиотеки
Tests in Swift
• Требования к тестам
• Что мы поменяли
• Swift-style тесты
• Советы и рекомендации
Tests in Swift
НИЧЕГО ☺
Tests in Swift
Tests in Swift
Tests in Swift
Tests in Swift
Tests in Swift
• Требования к тестам
• Что мы поменяли
• Swift-style тесты
• Советы и рекомендации
Tests in Swift
Assertions
Tests in Swift
Assertions
public func XCTFail(_ message: String = default,
file: StaticString = #file,
line: UInt = #line)
Tests in Swift
func testExample() {
// given
// when
// then
XCTFail("message")
}
Assertions
Tests in Swift
func testExample() {
// given
let pair = (20, 16)
// when
// then
XCTAssertEqual(pair, (20, 17))
}
Assertions
Tests in Swift
func testExample() {
// given
let pair = (20, 16)
// when
// then
XCTAssertEqual(pair, (20, 17))
}
Assertions
Tests in Swift
func testExample() {
// given
let pair = (20, 16)
// when
// then
XCTAssert(pair == (20, 17))
}
Assertions
Tests in Swift
Assertions
Tests in Swift
Assertions
Tests in Swift
Write a custom test assertion
Tests in Swift
func assertIntPairsEqual(actual: (_: Int, _: Int),
expected: (_: Int, _: Int),
file: StaticString = #file,
line: UInt = #line) {
if actual != expected {
XCTFail("Expected (expected) but was (actual)",
file: file, line: line)
}
}
Custom assertions
Tests in Swift
func assertIntPairsEqual(actual: (_: Int, _: Int),
expected: (_: Int, _: Int),
file: StaticString = #file,
line: UInt = #line) {
if actual != expected {
XCTFail("Expected (expected) but was (actual)",
file: file, line: line)
}
}
Custom assertions
Tests in Swift
func assertIntPairsEqual(actual: (_: Int, _: Int),
expected: (_: Int, _: Int),
file: StaticString = #file,
line: UInt = #line) {
if actual != expected {
XCTFail("Expected (expected) but was (actual)",
file: file, line: line)
}
}
Custom assertions
Tests in Swift
Custom assertions
Tests in Swift
func assertPairsEqual<T: Equatable, U: Equatable>(actual: (_: T, _: U),
expected: (_: T, _: U),
file: StaticString = #file,
line: UInt = #line)
Custom assertions
Tests in Swift
Mocks
Tests in Swift
Mocks
NSURLSession *mock = OCMClassMock([NSURLSession class]);
Tests in Swift
Mocks
NSURLSession *mock = OCMClassMock([NSURLSession class]);
Tests in Swift
Protocols and Extensions
Tests in Swift
struct ToDoService {
init(session: URLSession) {
// …
}
}
let service = ToDoService(session: URLSession.shared)
Mocks
Tests in Swift
struct ToDoService {
init(session: URLSession) {
// …
}
}
let service = ToDoService(session: URLSession.shared)
Mocks
Tests in Swift
protocol URLSessionProtocol {}
extension URLSession: URLSessionProtocol {}
struct ToDoService {
init(session: URLSessionProtocol) {
// …
}
}
Mocks
Tests in Swift
protocol URLSessionProtocol {}
extension URLSession: URLSessionProtocol {}
struct ToDoService {
init(session: URLSessionProtocol) {
// …
}
}
Mocks
Tests in Swift
protocol URLSessionProtocol {}
extension URLSession: URLSessionProtocol {}
struct ToDoService {
init(session: URLSessionProtocol) {
// …
}
}
Mocks
Tests in Swift
protocol URLSessionProtocol {}
extension URLSession: URLSessionProtocol {}
struct ToDoService {
init(session: URLSessionProtocol) {
// …
}
}
Mocks
Tests in Swift
let service = ToDoService(session: URLSession.shared)
Mocks
Tests in Swift
class MockURLSession: URLSessionProtocol {}
Mocks
Tests in Swift
protocol URLSessionProtocol {
func dataTask(request: URLRequest,
completion:(Data?, Response?, Error?) ->
Void) -> DataTask
}
Mocks
Tests in Swift
class MockURLSession: URLSessionProtocol {
func dataTask(with …) -> URLSessionDataTask {
return URLSessionDataTask()
}
}
Mocks
Tests in Swift
1. Число вызовов
2. Переданные аргументы
Mocks
Tests in Swift
class MockURLSession: URLSessionProtocol {
var dataTaskCallCount = 0
func dataTask(with …) -> URLSessionDataTask {
dataTaskCallCount += 1
return URLSessionDataTask()
}
}
Mocks
Tests in Swift
func testExample() {
// given
...
// when
...
// then
XCTAssertEqual(mockURLSession.dataTaskCallCount, 1)
}
Mocks
Tests in Swift
class MockURLSession: URLSessionProtocol {
var dataTaskLastURL: URL?
func dataTask(with …) -> URLSessionDataTask {
dataTaskLastURL = url
return URLSessionDataTask()
}
}
Mocks
Tests in Swift
func testExample() {
// given
...
// when
...
// then
XCTAssertEqual(mockURLSession.dataTaskLastURL?.host,
"http://expected.com")
}
Mocks
Tests in Swift
1. Используйте вспомогательные методы
для одной проверки
2. Накапливайте аргументы в коллекциях
Mocks
Tests in Swift
Partial mocks
Tests in Swift
class MockURLSessionDataTask: URLSessionDataTask {
private var resumeCallCount = 0
override func resume() {
resumeCallCount += 1
}
func verifyResume() {
XCTAssertEqual(resumeCallCount, 1)
}
}
Partial mocks
Tests in Swift
class MockURLSession: URLSessionProtocol {
var dataTaskReturnValue: URLSessionDataTask!
func dataTask(with …) -> URLSessionDataTask {
return dataTaskReturnValue
}
}
Partial mocks
Tests in Swift
func testExample() {
// given
let mockDataTask = MockURLSessionDataTask()
mockURLSession.dataTaskReturnValue = mockDataTask
// when
…
// then
mockDataTask.verifyResume()
}
Partial mocks
Tests in Swift
Partial mocks
💩
Tests in Swift
Mocks
• Избегайте моков
• Используйте Protocols
• Используйте Partial mocks
Tests in Swift
Optionals
Tests in Swift
func testAnOptional() throws {
// given
let string: String? = nil
// when
...
// then
guard let newString = string else { throw
UnexpectedNilError() }
XCTAssert(newString.lengthOfBytes(using: .utf8) > 0)
}
Optionals
Tests in Swift
struct UnexpectedNilError: Error {}
func AssertNotNilAndUnwrap<T>(_ variable: T?,
message: String = "Error",
file: StaticString = #file,
line: UInt = #line) throws -> T {
guard let variable = variable else {
XCTFail(message, file: file, line: line)
throw UnexpectedNilError()
}
return variable
}
Optionals
Tests in Swift
• Требования к тестам
• Что мы поменяли
• Swift-style тесты
• Советы и рекомендации
Tests in Swift
Библиотеки
• SwiftyMock
• Kakapo 🐤
• Dobby
• MockFive
• Cuckoo
Tests in Swift
Библиотеки
• SwiftyMock
• Kakapo 🐤
• Dobby
• MockFive
• Cuckoo
Tests in Swift
Библиотеки
Tests in Swift
Библиотеки
OUTPUT_FILE="$PROJECT_DIR/Tests/
GeneratedMocks.swift"
echo "Generated Mocks File = $OUTPUT_FILE"
INPUT_DIR="$PROJECT_DIR"
echo "Mocks Input Directory = $INPUT_DIR"
${PODS_ROOT}/Cuckoo/run generate --testable
"$PROJECT_NAME" 
--output "${OUTPUT_FILE}" 
"$INPUT_DIR/FileName1.swift" 
"$INPUT_DIR/FileName2.swift" 
"$INPUT_DIR/FileName3.swift"
Tests in Swift
Библиотеки
OUTPUT_FILE="$PROJECT_DIR/Tests/
GeneratedMocks.swift"
echo "Generated Mocks File = $OUTPUT_FILE"
INPUT_DIR="$PROJECT_DIR"
echo "Mocks Input Directory = $INPUT_DIR"
${PODS_ROOT}/Cuckoo/run generate --testable
"$PROJECT_NAME" 
--output "${OUTPUT_FILE}" 
"$INPUT_DIR/FileName1.swift" 
"$INPUT_DIR/FileName2.swift" 
"$INPUT_DIR/FileName3.swift"
Tests in Swift
Библиотеки
Tests in Swift
Библиотеки
Tests in Swift
Playground
Tests in Swift
Playground
TodoTests.defaultTestSuite().run()
Tests in Swift
a.sychev@rambler-co.ru
@asychev89
What makes a clean test? Three things.
Readability, readability, and readability.
Robert C. Martin

More Related Content

PDF
RSpec 3.0: Under the Covers
PDF
Kotlin Receiver Types 介紹
PDF
Test-Driven Development of AngularJS Applications
PPTX
Functional Reactive Programming (FRP): Working with RxJS
ODP
Lambda Chops - Recipes for Simpler, More Expressive Code
ODP
Getting to Grips with SilverStripe Testing
PDF
Java, what's next?
PDF
Write code that writes code!
RSpec 3.0: Under the Covers
Kotlin Receiver Types 介紹
Test-Driven Development of AngularJS Applications
Functional Reactive Programming (FRP): Working with RxJS
Lambda Chops - Recipes for Simpler, More Expressive Code
Getting to Grips with SilverStripe Testing
Java, what's next?
Write code that writes code!

What's hot (20)

PDF
Reactive programming on Android
PPTX
Java concurrency questions and answers
PDF
BDD style Unit Testing
PDF
Productive Programming in Java 8 - with Lambdas and Streams
PPT
Building a java tracer
PDF
Unit Testing in SilverStripe
PDF
Modern Programming in Java 8 - Lambdas, Streams and Date Time API
KEY
Java to Scala: Why & How
PDF
JavaScript For CSharp Developer
PDF
Serializing EMF models with Xtext
PDF
Scala coated JVM
PDF
C# - What's Next?
PDF
Scala in-practice-3-years by Patric Fornasier, Springr, presented at Pune Sca...
PDF
Nikita Popov "What’s new in PHP 8.0?"
PPTX
From Ruby to Scala
KEY
LSUG: How we (mostly) moved from Java to Scala
PDF
Ruby 程式語言綜覽簡介
PPTX
Mastering java bytecode with ASM - GeeCON 2012
PPTX
Mastering Java Bytecode - JAX.de 2012
PPT
Introduction to Javascript
Reactive programming on Android
Java concurrency questions and answers
BDD style Unit Testing
Productive Programming in Java 8 - with Lambdas and Streams
Building a java tracer
Unit Testing in SilverStripe
Modern Programming in Java 8 - Lambdas, Streams and Date Time API
Java to Scala: Why & How
JavaScript For CSharp Developer
Serializing EMF models with Xtext
Scala coated JVM
C# - What's Next?
Scala in-practice-3-years by Patric Fornasier, Springr, presented at Pune Sca...
Nikita Popov "What’s new in PHP 8.0?"
From Ruby to Scala
LSUG: How we (mostly) moved from Java to Scala
Ruby 程式語言綜覽簡介
Mastering java bytecode with ASM - GeeCON 2012
Mastering Java Bytecode - JAX.de 2012
Introduction to Javascript
Ad

Similar to «Как сделать так, чтобы тесты на Swift не причиняли боль» Сычев Александр, Rambler&Co (20)

PDF
Android Automated Testing
PDF
10 Techniques to writing easy yet stupidly thorough unit tests.pdf
PDF
Tdd iPhone For Dummies
PDF
Just Do It! ColdBox Integration Testing
PDF
Sleipnir presentation
PDF
Why Spring <3 Kotlin
KEY
Getting the most out of Java [Nordic Coding-2010]
PDF
power-assert, mechanism and philosophy
PDF
JavaScript and the AST
KEY
Unit testing en iOS @ MobileCon Galicia
PDF
API first with Swagger and Scala by Slava Schmidt
PDF
Swift와 Objective-C를 함께 쓰는 방법
PPTX
Java Annotations and Pre-processing
PDF
Unit testing in iOS featuring OCUnit, GHUnit & OCMock
PPT
Scala uma poderosa linguagem para a jvm
PDF
Swift, functional programming, and the future of Objective-C
PDF
Denis Lebedev, Swift
PDF
A Lifecycle Of Code Under Test by Robert Fornal
PDF
Functional Core, Reactive Shell
PPTX
Thinking in swift ppt
Android Automated Testing
10 Techniques to writing easy yet stupidly thorough unit tests.pdf
Tdd iPhone For Dummies
Just Do It! ColdBox Integration Testing
Sleipnir presentation
Why Spring <3 Kotlin
Getting the most out of Java [Nordic Coding-2010]
power-assert, mechanism and philosophy
JavaScript and the AST
Unit testing en iOS @ MobileCon Galicia
API first with Swagger and Scala by Slava Schmidt
Swift와 Objective-C를 함께 쓰는 방법
Java Annotations and Pre-processing
Unit testing in iOS featuring OCUnit, GHUnit & OCMock
Scala uma poderosa linguagem para a jvm
Swift, functional programming, and the future of Objective-C
Denis Lebedev, Swift
A Lifecycle Of Code Under Test by Robert Fornal
Functional Core, Reactive Shell
Thinking in swift ppt
Ad

More from it-people (20)

PDF
«Про аналитику и серебряные пули» Александр Подсобляев, Rambler&Co
PDF
«Scrapy internals» Александр Сибиряков, Scrapinghub
PDF
«Отладка в Python 3.6: Быстрее, Выше, Сильнее» Елизавета Шашкова, JetBrains
PDF
«Gevent — быть или не быть?» Александр Мокров, Positive Technologies
PDF
«Ещё один Поиск Яндекса» Александр Кошелев, Яндекс
PDF
«How I Learned to Stop Worrying and Love the BFG: нагрузочное тестирование со...
PDF
«Write once run anywhere — почём опиум для народа?» Игорь Новиков, Scalr
PDF
«Gensim — тематическое моделирование для людей» Иван Меньших, Лев Константино...
PDF
«Тотальный контроль производительности» Михаил Юматов, ЦИАН
PDF
«Детские болезни live-чата» Ольга Сентемова, Тинькофф Банк
PDF
«Микросервисы наносят ответный удар!» Олег Чуркин, Rambler&Co
PDF
«Память и Python. Что надо знать для счастья?» Алексей Кузьмин, ЦНС
PDF
«Что такое serverless-архитектура и как с ней жить?» Николай Марков, Aligned ...
PDF
«Python на острие бритвы: PyPy project» Александр Кошкин, Positive Technologies
PDF
«PyWat. А хорошо ли вы знаете Python?» Александр Швец, Marilyn System
PDF
«(Без)опасный Python», Иван Цыганов, Positive Technologies
PDF
«Python of Things», Кирилл Борисов, Яндекс
PDF
«Клиенту и серверу нужно поговорить» Прокопов Никита, Cognician
PDF
«Кошелек или деньги: сложный выбор между памятью и процессором» Алексеенко Иг...
PDF
ЗАВИСИМОСТИ В КОМПОНЕНТНОМ ВЕБЕ, ПРИГОТОВЛЕННЫЕ ПРАВИЛЬНО, Гриненко Владимир,...
«Про аналитику и серебряные пули» Александр Подсобляев, Rambler&Co
«Scrapy internals» Александр Сибиряков, Scrapinghub
«Отладка в Python 3.6: Быстрее, Выше, Сильнее» Елизавета Шашкова, JetBrains
«Gevent — быть или не быть?» Александр Мокров, Positive Technologies
«Ещё один Поиск Яндекса» Александр Кошелев, Яндекс
«How I Learned to Stop Worrying and Love the BFG: нагрузочное тестирование со...
«Write once run anywhere — почём опиум для народа?» Игорь Новиков, Scalr
«Gensim — тематическое моделирование для людей» Иван Меньших, Лев Константино...
«Тотальный контроль производительности» Михаил Юматов, ЦИАН
«Детские болезни live-чата» Ольга Сентемова, Тинькофф Банк
«Микросервисы наносят ответный удар!» Олег Чуркин, Rambler&Co
«Память и Python. Что надо знать для счастья?» Алексей Кузьмин, ЦНС
«Что такое serverless-архитектура и как с ней жить?» Николай Марков, Aligned ...
«Python на острие бритвы: PyPy project» Александр Кошкин, Positive Technologies
«PyWat. А хорошо ли вы знаете Python?» Александр Швец, Marilyn System
«(Без)опасный Python», Иван Цыганов, Positive Technologies
«Python of Things», Кирилл Борисов, Яндекс
«Клиенту и серверу нужно поговорить» Прокопов Никита, Cognician
«Кошелек или деньги: сложный выбор между памятью и процессором» Алексеенко Иг...
ЗАВИСИМОСТИ В КОМПОНЕНТНОМ ВЕБЕ, ПРИГОТОВЛЕННЫЕ ПРАВИЛЬНО, Гриненко Владимир,...

Recently uploaded (20)

PPTX
Introuction about ICD -10 and ICD-11 PPT.pptx
PPTX
PptxGenJS_Demo_Chart_20250317130215833.pptx
PDF
Triggering QUIC, presented by Geoff Huston at IETF 123
PDF
💰 𝐔𝐊𝐓𝐈 𝐊𝐄𝐌𝐄𝐍𝐀𝐍𝐆𝐀𝐍 𝐊𝐈𝐏𝐄𝐑𝟒𝐃 𝐇𝐀𝐑𝐈 𝐈𝐍𝐈 𝟐𝟎𝟐𝟓 💰
PDF
Tenda Login Guide: Access Your Router in 5 Easy Steps
PPTX
INTERNET------BASICS-------UPDATED PPT PRESENTATION
PDF
The New Creative Director: How AI Tools for Social Media Content Creation Are...
PPTX
CHE NAA, , b,mn,mblblblbljb jb jlb ,j , ,C PPT.pptx
PDF
WebRTC in SignalWire - troubleshooting media negotiation
PPTX
introduction about ICD -10 & ICD-11 ppt.pptx
PDF
Automated vs Manual WooCommerce to Shopify Migration_ Pros & Cons.pdf
PDF
Cloud-Scale Log Monitoring _ Datadog.pdf
PPTX
innovation process that make everything different.pptx
PDF
SASE Traffic Flow - ZTNA Connector-1.pdf
PDF
Testing WebRTC applications at scale.pdf
PPTX
QR Codes Qr codecodecodecodecocodedecodecode
PPT
Design_with_Watersergyerge45hrbgre4top (1).ppt
PPTX
artificial intelligence overview of it and more
PPTX
522797556-Unit-2-Temperature-measurement-1-1.pptx
PDF
APNIC Update, presented at PHNOG 2025 by Shane Hermoso
Introuction about ICD -10 and ICD-11 PPT.pptx
PptxGenJS_Demo_Chart_20250317130215833.pptx
Triggering QUIC, presented by Geoff Huston at IETF 123
💰 𝐔𝐊𝐓𝐈 𝐊𝐄𝐌𝐄𝐍𝐀𝐍𝐆𝐀𝐍 𝐊𝐈𝐏𝐄𝐑𝟒𝐃 𝐇𝐀𝐑𝐈 𝐈𝐍𝐈 𝟐𝟎𝟐𝟓 💰
Tenda Login Guide: Access Your Router in 5 Easy Steps
INTERNET------BASICS-------UPDATED PPT PRESENTATION
The New Creative Director: How AI Tools for Social Media Content Creation Are...
CHE NAA, , b,mn,mblblblbljb jb jlb ,j , ,C PPT.pptx
WebRTC in SignalWire - troubleshooting media negotiation
introduction about ICD -10 & ICD-11 ppt.pptx
Automated vs Manual WooCommerce to Shopify Migration_ Pros & Cons.pdf
Cloud-Scale Log Monitoring _ Datadog.pdf
innovation process that make everything different.pptx
SASE Traffic Flow - ZTNA Connector-1.pdf
Testing WebRTC applications at scale.pdf
QR Codes Qr codecodecodecodecocodedecodecode
Design_with_Watersergyerge45hrbgre4top (1).ppt
artificial intelligence overview of it and more
522797556-Unit-2-Temperature-measurement-1-1.pptx
APNIC Update, presented at PHNOG 2025 by Shane Hermoso

«Как сделать так, чтобы тесты на Swift не причиняли боль» Сычев Александр, Rambler&Co

  • 1. Александр Сычев Как сделать так, чтобы тесты на Swift не причиняли боль 🤕
  • 2. Tests in Swift Александр Сычев a.sychev@rambler-co.ru @asychev89
  • 3. Tests in Swift • Требования к тестам • Что мы поменяли • Swift-style тесты • Советы и рекомендации
  • 7. Tests in Swift Преимущества • Понимание требований • Стабильность при рефакторинге • Документация • Хороший API • Меньше ошибок
  • 8. Tests in Swift Преимущества • Понимание требований • Стабильность при рефакторинге • Документация • Хороший API • Меньше ошибок
  • 9. Tests in Swift Преимущества • Понимание требований • Стабильность при рефакторинге • Документация • Хороший API • Меньше ошибок
  • 10. Tests in Swift Преимущества • Понимание требований • Стабильность при рефакторинге • Документация • Хороший API • Меньше ошибок
  • 11. Tests in Swift Преимущества • Понимание требований • Стабильность при рефакторинге • Документация • Хороший API • Меньше ошибок
  • 14. Tests in Swift - (void)testThatObjectsWithEqualTitleAreEqual { // given RubricsObject *object1 = [[RubricsObject alloc] initWithTagID:@"tagID1" title:@"Title"]; RubricsObject *object2 = [[RubricsObject alloc] initWithTagID:@"tagID2" title:@"Title"]; // when BOOL equal = [object1 isEqual:object2]; // then XCTAssertTrue(equal); }
  • 15. Tests in Swift - (void)testThatObjectsWithEqualTitleAreEqual { // given RubricsObject *object1 = [[RubricsObject alloc] initWithTagID:@"tagID1" title:@"Title"]; RubricsObject *object2 = [[RubricsObject alloc] initWithTagID:@"tagID2" title:@"Title"]; // when BOOL equal = [object1 isEqual:object2]; // then XCTAssertTrue(equal); } // given // when // then
  • 16. Tests in Swift - (void)testThatObjectsWithEqualTitleAreEqual { // given RubricsObject *object1 = [[RubricsObject alloc] initWithTagID:@"tagID1" title:@"Title"]; RubricsObject *object2 = [[RubricsObject alloc] initWithTagID:@"tagID2" title:@"Title"]; // when BOOL equal = [object1 isEqual:object2]; // then XCTAssertTrue(equal); } // given // when // then
  • 17. Tests in Swift - (void)testThatObjectsWithEqualTitleAreEqual { // given RubricsObject *object1 = [[RubricsObject alloc] initWithTagID:@"tagID1" title:@"Title"]; RubricsObject *object2 = [[RubricsObject alloc] initWithTagID:@"tagID2" title:@"Title"]; // when BOOL equal = [object1 isEqual:object2]; // then XCTAssertTrue(equal); } // given // when // then
  • 18. Tests in Swift - (void)testThatObjectsWithEqualTitleAreEqual { // given RubricsObject *object1 = [[RubricsObject alloc] initWithTagID:@"tagID1" title:@"Title"]; RubricsObject *object2 = [[RubricsObject alloc] initWithTagID:@"tagID2" title:@"Title"]; // when BOOL equal = [object1 isEqual:object2]; // then XCTAssertTrue(equal); } // given // when // then
  • 19. Tests in Swift What makes a clean test? Three things. Readability, readability, and readability. Robert C. Martin
  • 20. Tests in Swift • Предметно-ориентированный язык • Нет лишнего контекста • Проверка одной истины Чистый тест
  • 21. Tests in Swift Предметно-ориентированный язык - (void)test1 { RubricsObject *obj = [RubricsObject new]; XCTAssertEqualObjects(obj1, obj2); }
  • 22. Tests in Swift Предметно-ориентированный язык - (void)test1 { RubricsObject *obj = [RubricsObject new]; XCTAssertEqualObjects(obj1, obj2); }
  • 24. Tests in Swift - (void)testThatObjectsWithEqualTitleAreEqual { // given RubricsObject *object1 = [[RubricsObject alloc] initWithTagID:@"tagID1" title:@"Title"]; RubricsObject *object2 = [[RubricsObject alloc] initWithTagID:@"tagID2" title:@"Title"]; // when BOOL equal = [object1 isEqual:object2]; // then XCTAssertTrue(equal); }
  • 25. Tests in Swift Нет лишнего контекста - (void)testThatMailBoxServiceObtainesCachedConnectedMailBoxConfigList { // given NSString *firstMailBoxNativeFolderName = @"firstFolder"; NSString *secondMailBoxNativeFolderName = @"secondFolder"; NSManagedObjectContext *managedObjectContext = [NSManagedObjectContext MR_rootSavingContext]; [managedObjectContext performBlockAndWait:^{ RCMMailBox *firstMailBox = [RCMMailBox MR_createEntityInContext:managedObjectContext]; firstMailBox.nativeFolder = firstMailBoxNativeFolderName; RCMMailBox *secondMailBox = [RCMMailBox MR_createEntityInContext:managedObjectContext]; secondMailBox.nativeFolder = secondMailBoxNativeFolderName; [managedObjectContext MR_saveOnlySelfAndWait]; }]; // when NSArray *mailBoxList = [self.mailBoxService obtainCachedConnectedMailBoxConfigList]; // then BOOL containsFirstMailBox; BOOL containsSecondMailBox; for (id<RCMMailBoxConfig> mailBoxConfig in mailBoxList) { if ([mailBoxConfig.nativeFolder isEqualToString:firstMailBoxNativeFolderName]) { containsFirstMailBox = YES; } else if ([mailBoxConfig.nativeFolder isEqualToString:secondMailBoxNativeFolderName]) { containsSecondMailBox = YES; } } XCTAssertTrue(containsFirstMailBox); XCTAssertTrue(containsSecondMailBox); }
  • 26. Tests in Swift Нет лишнего контекста - (void)testThatMailBoxServiceObtainesCachedConnectedMailBoxConfigList { // given NSString *firstMailBoxNativeFolderName = @"firstFolder"; NSString *secondMailBoxNativeFolderName = @"secondFolder"; NSManagedObjectContext *managedObjectContext = [NSManagedObjectContext MR_rootSavingContext]; [managedObjectContext performBlockAndWait:^{ RCMMailBox *firstMailBox = [RCMMailBox MR_createEntityInContext:managedObjectContext]; firstMailBox.nativeFolder = firstMailBoxNativeFolderName; RCMMailBox *secondMailBox = [RCMMailBox MR_createEntityInContext:managedObjectContext]; secondMailBox.nativeFolder = secondMailBoxNativeFolderName; [managedObjectContext MR_saveOnlySelfAndWait]; }]; // when NSArray *mailBoxList = [self.mailBoxService obtainCachedConnectedMailBoxConfigList]; // then BOOL containsFirstMailBox; BOOL containsSecondMailBox; for (id<RCMMailBoxConfig> mailBoxConfig in mailBoxList) { if ([mailBoxConfig.nativeFolder isEqualToString:firstMailBoxNativeFolderName]) { containsFirstMailBox = YES; } else if ([mailBoxConfig.nativeFolder isEqualToString:secondMailBoxNativeFolderName]) { containsSecondMailBox = YES; } } XCTAssertTrue(containsFirstMailBox); XCTAssertTrue(containsSecondMailBox); } 😱
  • 28. Tests in Swift Проверка одной истины assert one truth
  • 29. Tests in Swift Требования • Скорость • Надежность
  • 30. Tests in Swift Требования • Скорость • Надежность 🚀
  • 31. Tests in Swift Требования • Скорость • Надежность
  • 33. Tests in Swift • Требования к тестам • Что мы поменяли • Swift-style тесты • Советы и рекомендации
  • 39. Tests in Swift • Требования к тестам • Что мы поменяли • Swift-style тесты • Советы и рекомендации
  • 41. Tests in Swift Assertions public func XCTFail(_ message: String = default, file: StaticString = #file, line: UInt = #line)
  • 42. Tests in Swift func testExample() { // given // when // then XCTFail("message") } Assertions
  • 43. Tests in Swift func testExample() { // given let pair = (20, 16) // when // then XCTAssertEqual(pair, (20, 17)) } Assertions
  • 44. Tests in Swift func testExample() { // given let pair = (20, 16) // when // then XCTAssertEqual(pair, (20, 17)) } Assertions
  • 45. Tests in Swift func testExample() { // given let pair = (20, 16) // when // then XCTAssert(pair == (20, 17)) } Assertions
  • 48. Tests in Swift Write a custom test assertion
  • 49. Tests in Swift func assertIntPairsEqual(actual: (_: Int, _: Int), expected: (_: Int, _: Int), file: StaticString = #file, line: UInt = #line) { if actual != expected { XCTFail("Expected (expected) but was (actual)", file: file, line: line) } } Custom assertions
  • 50. Tests in Swift func assertIntPairsEqual(actual: (_: Int, _: Int), expected: (_: Int, _: Int), file: StaticString = #file, line: UInt = #line) { if actual != expected { XCTFail("Expected (expected) but was (actual)", file: file, line: line) } } Custom assertions
  • 51. Tests in Swift func assertIntPairsEqual(actual: (_: Int, _: Int), expected: (_: Int, _: Int), file: StaticString = #file, line: UInt = #line) { if actual != expected { XCTFail("Expected (expected) but was (actual)", file: file, line: line) } } Custom assertions
  • 52. Tests in Swift Custom assertions
  • 53. Tests in Swift func assertPairsEqual<T: Equatable, U: Equatable>(actual: (_: T, _: U), expected: (_: T, _: U), file: StaticString = #file, line: UInt = #line) Custom assertions
  • 55. Tests in Swift Mocks NSURLSession *mock = OCMClassMock([NSURLSession class]);
  • 56. Tests in Swift Mocks NSURLSession *mock = OCMClassMock([NSURLSession class]);
  • 57. Tests in Swift Protocols and Extensions
  • 58. Tests in Swift struct ToDoService { init(session: URLSession) { // … } } let service = ToDoService(session: URLSession.shared) Mocks
  • 59. Tests in Swift struct ToDoService { init(session: URLSession) { // … } } let service = ToDoService(session: URLSession.shared) Mocks
  • 60. Tests in Swift protocol URLSessionProtocol {} extension URLSession: URLSessionProtocol {} struct ToDoService { init(session: URLSessionProtocol) { // … } } Mocks
  • 61. Tests in Swift protocol URLSessionProtocol {} extension URLSession: URLSessionProtocol {} struct ToDoService { init(session: URLSessionProtocol) { // … } } Mocks
  • 62. Tests in Swift protocol URLSessionProtocol {} extension URLSession: URLSessionProtocol {} struct ToDoService { init(session: URLSessionProtocol) { // … } } Mocks
  • 63. Tests in Swift protocol URLSessionProtocol {} extension URLSession: URLSessionProtocol {} struct ToDoService { init(session: URLSessionProtocol) { // … } } Mocks
  • 64. Tests in Swift let service = ToDoService(session: URLSession.shared) Mocks
  • 65. Tests in Swift class MockURLSession: URLSessionProtocol {} Mocks
  • 66. Tests in Swift protocol URLSessionProtocol { func dataTask(request: URLRequest, completion:(Data?, Response?, Error?) -> Void) -> DataTask } Mocks
  • 67. Tests in Swift class MockURLSession: URLSessionProtocol { func dataTask(with …) -> URLSessionDataTask { return URLSessionDataTask() } } Mocks
  • 68. Tests in Swift 1. Число вызовов 2. Переданные аргументы Mocks
  • 69. Tests in Swift class MockURLSession: URLSessionProtocol { var dataTaskCallCount = 0 func dataTask(with …) -> URLSessionDataTask { dataTaskCallCount += 1 return URLSessionDataTask() } } Mocks
  • 70. Tests in Swift func testExample() { // given ... // when ... // then XCTAssertEqual(mockURLSession.dataTaskCallCount, 1) } Mocks
  • 71. Tests in Swift class MockURLSession: URLSessionProtocol { var dataTaskLastURL: URL? func dataTask(with …) -> URLSessionDataTask { dataTaskLastURL = url return URLSessionDataTask() } } Mocks
  • 72. Tests in Swift func testExample() { // given ... // when ... // then XCTAssertEqual(mockURLSession.dataTaskLastURL?.host, "http://expected.com") } Mocks
  • 73. Tests in Swift 1. Используйте вспомогательные методы для одной проверки 2. Накапливайте аргументы в коллекциях Mocks
  • 75. Tests in Swift class MockURLSessionDataTask: URLSessionDataTask { private var resumeCallCount = 0 override func resume() { resumeCallCount += 1 } func verifyResume() { XCTAssertEqual(resumeCallCount, 1) } } Partial mocks
  • 76. Tests in Swift class MockURLSession: URLSessionProtocol { var dataTaskReturnValue: URLSessionDataTask! func dataTask(with …) -> URLSessionDataTask { return dataTaskReturnValue } } Partial mocks
  • 77. Tests in Swift func testExample() { // given let mockDataTask = MockURLSessionDataTask() mockURLSession.dataTaskReturnValue = mockDataTask // when … // then mockDataTask.verifyResume() } Partial mocks
  • 79. Tests in Swift Mocks • Избегайте моков • Используйте Protocols • Используйте Partial mocks
  • 81. Tests in Swift func testAnOptional() throws { // given let string: String? = nil // when ... // then guard let newString = string else { throw UnexpectedNilError() } XCTAssert(newString.lengthOfBytes(using: .utf8) > 0) } Optionals
  • 82. Tests in Swift struct UnexpectedNilError: Error {} func AssertNotNilAndUnwrap<T>(_ variable: T?, message: String = "Error", file: StaticString = #file, line: UInt = #line) throws -> T { guard let variable = variable else { XCTFail(message, file: file, line: line) throw UnexpectedNilError() } return variable } Optionals
  • 83. Tests in Swift • Требования к тестам • Что мы поменяли • Swift-style тесты • Советы и рекомендации
  • 84. Tests in Swift Библиотеки • SwiftyMock • Kakapo 🐤 • Dobby • MockFive • Cuckoo
  • 85. Tests in Swift Библиотеки • SwiftyMock • Kakapo 🐤 • Dobby • MockFive • Cuckoo
  • 87. Tests in Swift Библиотеки OUTPUT_FILE="$PROJECT_DIR/Tests/ GeneratedMocks.swift" echo "Generated Mocks File = $OUTPUT_FILE" INPUT_DIR="$PROJECT_DIR" echo "Mocks Input Directory = $INPUT_DIR" ${PODS_ROOT}/Cuckoo/run generate --testable "$PROJECT_NAME" --output "${OUTPUT_FILE}" "$INPUT_DIR/FileName1.swift" "$INPUT_DIR/FileName2.swift" "$INPUT_DIR/FileName3.swift"
  • 88. Tests in Swift Библиотеки OUTPUT_FILE="$PROJECT_DIR/Tests/ GeneratedMocks.swift" echo "Generated Mocks File = $OUTPUT_FILE" INPUT_DIR="$PROJECT_DIR" echo "Mocks Input Directory = $INPUT_DIR" ${PODS_ROOT}/Cuckoo/run generate --testable "$PROJECT_NAME" --output "${OUTPUT_FILE}" "$INPUT_DIR/FileName1.swift" "$INPUT_DIR/FileName2.swift" "$INPUT_DIR/FileName3.swift"
  • 93. Tests in Swift a.sychev@rambler-co.ru @asychev89 What makes a clean test? Three things. Readability, readability, and readability. Robert C. Martin