Download - Rambler.iOS #8 - Чистые unit-тесты
![Page 1: Rambler.iOS #8 - Чистые unit-тесты](https://reader034.vdocuments.net/reader034/viewer/2022042723/58847e821a28ab5e248b7b49/html5/thumbnails/1.jpg)
Чистые unit-тесты
![Page 2: Rambler.iOS #8 - Чистые unit-тесты](https://reader034.vdocuments.net/reader034/viewer/2022042723/58847e821a28ab5e248b7b49/html5/thumbnails/2.jpg)
What makes a clean test?
Three things. Readability, readability, and readability.
Robert C. Martin, «Clean Code»
![Page 3: Rambler.iOS #8 - Чистые unit-тесты](https://reader034.vdocuments.net/reader034/viewer/2022042723/58847e821a28ab5e248b7b49/html5/thumbnails/3.jpg)
#добришко
![Page 4: Rambler.iOS #8 - Чистые unit-тесты](https://reader034.vdocuments.net/reader034/viewer/2022042723/58847e821a28ab5e248b7b49/html5/thumbnails/4.jpg)
- (void)testLoadAlbums { NSError *err1 = [NSError errorWithDomain:@"" code:123 userInfo:nil];
OCMStub([self.provider getAlbumsWithResultBlock:OCMOCK_ANY]).andDo(block);
[self waitForExpectationsWithTimeout:1. handler:^(NSError *localError) { OCMVerify([self.provider getAlbumsWithResultBlock:OCMOCK_ANY]); }]; }
4
![Page 5: Rambler.iOS #8 - Чистые unit-тесты](https://reader034.vdocuments.net/reader034/viewer/2022042723/58847e821a28ab5e248b7b49/html5/thumbnails/5.jpg)
- (void)testLoadAlbums { NSError *err1 = [NSError errorWithDomain:@"" code:123 userInfo:nil];
OCMStub([self.provider getAlbumsWithResultBlock:OCMOCK_ANY]).andDo(block);
[self waitForExpectationsWithTimeout:1. handler:^(NSError *localError) { OCMVerify([self.provider getAlbumsWithResultBlock:OCMOCK_ANY]); }]; }
5
![Page 6: Rambler.iOS #8 - Чистые unit-тесты](https://reader034.vdocuments.net/reader034/viewer/2022042723/58847e821a28ab5e248b7b49/html5/thumbnails/6.jpg)
- (void)testLoadAlbums { NSError *err1 = [NSError errorWithDomain:@"" code:123 userInfo:nil];
OCMStub([self.provider getAlbumsWithResultBlock:OCMOCK_ANY]).andDo(block);
[self waitForExpectationsWithTimeout:1. handler:^(NSError *localError) { OCMVerify([self.provider getAlbumsWithResultBlock:OCMOCK_ANY]); }]; }
6
![Page 7: Rambler.iOS #8 - Чистые unit-тесты](https://reader034.vdocuments.net/reader034/viewer/2022042723/58847e821a28ab5e248b7b49/html5/thumbnails/7.jpg)
- (void)testLoadAlbums { NSError *err1 = [NSError errorWithDomain:@"" code:123 userInfo:nil];
OCMStub([self.provider getAlbumsWithResultBlock:OCMOCK_ANY]).andDo(block);
[self waitForExpectationsWithTimeout:1. handler:^(NSError *localError) { OCMVerify([self.provider getAlbumsWithResultBlock:OCMOCK_ANY]); }]; }
7
![Page 8: Rambler.iOS #8 - Чистые unit-тесты](https://reader034.vdocuments.net/reader034/viewer/2022042723/58847e821a28ab5e248b7b49/html5/thumbnails/8.jpg)
- (void)testDeletingMessages { NSMutableArray *messages = [NSMutableArray new]; NSManagedObjectContext *ctx = [NSManagedObjectContext MR_contextForCurrentThread]; [messages addObject:message]; } XCTestExpectation *expectation = [self expectationWithDescription: [NSString stringWithFormat:@"%s", __PRETTY_FUNCTION__]]; [self.service deleteMessages:messages withResultBlock:^(NSError *error) { [expectation fulfill]; }];
}
8
![Page 9: Rambler.iOS #8 - Чистые unit-тесты](https://reader034.vdocuments.net/reader034/viewer/2022042723/58847e821a28ab5e248b7b49/html5/thumbnails/9.jpg)
- (void)testDeletingMessages { NSMutableArray *messages = [NSMutableArray new]; NSManagedObjectContext *ctx = [NSManagedObjectContext MR_contextForCurrentThread]; [messages addObject:message]; } XCTestExpectation *expectation = [self expectationWithDescription: [NSString stringWithFormat:@"%s", __PRETTY_FUNCTION__]]; [self.service deleteMessages:messages withResultBlock:^(NSError *error) { [expectation fulfill]; }];
}
9
![Page 10: Rambler.iOS #8 - Чистые unit-тесты](https://reader034.vdocuments.net/reader034/viewer/2022042723/58847e821a28ab5e248b7b49/html5/thumbnails/10.jpg)
- (void)testDeletingMessages { NSMutableArray *messages = [NSMutableArray new]; NSManagedObjectContext *ctx = [NSManagedObjectContext MR_contextForCurrentThread]; [messages addObject:message]; } XCTestExpectation *expectation = [self expectationWithDescription: [NSString stringWithFormat:@"%s", __PRETTY_FUNCTION__]]; [self.service deleteMessages:messages withResultBlock:^(NSError *error) { [expectation fulfill]; }];
}
10
![Page 11: Rambler.iOS #8 - Чистые unit-тесты](https://reader034.vdocuments.net/reader034/viewer/2022042723/58847e821a28ab5e248b7b49/html5/thumbnails/11.jpg)
- (void)testDeletingMessages { NSMutableArray *messages = [NSMutableArray new]; NSManagedObjectContext *ctx = [NSManagedObjectContext MR_contextForCurrentThread]; [messages addObject:message]; } XCTestExpectation *expectation = [self expectationWithDescription: [NSString stringWithFormat:@"%s", __PRETTY_FUNCTION__]]; [self.service deleteMessages:messages withResultBlock:^(NSError *error) { [expectation fulfill]; }];
}
11
![Page 12: Rambler.iOS #8 - Чистые unit-тесты](https://reader034.vdocuments.net/reader034/viewer/2022042723/58847e821a28ab5e248b7b49/html5/thumbnails/12.jpg)
Зачем нужны чистые тесты
Как писать чистые тесты
Рефакторим тест
12
![Page 13: Rambler.iOS #8 - Чистые unit-тесты](https://reader034.vdocuments.net/reader034/viewer/2022042723/58847e821a28ab5e248b7b49/html5/thumbnails/13.jpg)
Зачем нужны чистые тесты
Как писать чистые тесты
Рефакторим тест
13
![Page 14: Rambler.iOS #8 - Чистые unit-тесты](https://reader034.vdocuments.net/reader034/viewer/2022042723/58847e821a28ab5e248b7b49/html5/thumbnails/14.jpg)
14
![Page 15: Rambler.iOS #8 - Чистые unit-тесты](https://reader034.vdocuments.net/reader034/viewer/2022042723/58847e821a28ab5e248b7b49/html5/thumbnails/15.jpg)
/** @author Egor Tolstoy Метод возвращает закешированные результаты поиска для определенной поисковой строки @param searchTerm Поисковая строка @return Результаты поиска */ - (NSArray *)obtainSearchResultsForSearchTerm:(NSString *)searchTerm;
15
![Page 16: Rambler.iOS #8 - Чистые unit-тесты](https://reader034.vdocuments.net/reader034/viewer/2022042723/58847e821a28ab5e248b7b49/html5/thumbnails/16.jpg)
@implementation PeopleServiceImplementationTests
- (void)testThatService {}
- (void)testThatService {}
- (void)testThatService {}
- (void)testThatService {}
@end
16
![Page 17: Rambler.iOS #8 - Чистые unit-тесты](https://reader034.vdocuments.net/reader034/viewer/2022042723/58847e821a28ab5e248b7b49/html5/thumbnails/17.jpg)
@implementation PeopleServiceImplementationTests
- (void)testThatService {}
- (void)testThatService {}
- (void)testThatService {}
- (void)testThatService {}
@end
17
![Page 18: Rambler.iOS #8 - Чистые unit-тесты](https://reader034.vdocuments.net/reader034/viewer/2022042723/58847e821a28ab5e248b7b49/html5/thumbnails/18.jpg)
@implementation PeopleServiceImplementationTests
- (void)testThatService {}
- (void)testThatService {}
- (void)testThatService {}
- (void)testThatService {}
@end
18
![Page 19: Rambler.iOS #8 - Чистые unit-тесты](https://reader034.vdocuments.net/reader034/viewer/2022042723/58847e821a28ab5e248b7b49/html5/thumbnails/19.jpg)
@implementation PeopleServiceImplementationTests
- (void)testThatService {}
- (void)testThatService {}
- (void)testThatService {}
- (void)testThatService {}
@end
19
![Page 20: Rambler.iOS #8 - Чистые unit-тесты](https://reader034.vdocuments.net/reader034/viewer/2022042723/58847e821a28ab5e248b7b49/html5/thumbnails/20.jpg)
@implementation PeopleServiceImplementationTests
- (void)testThatService {}
- (void)testThatService {}
- (void)testThatService {}
- (void)testThatService {}
@end
20
![Page 21: Rambler.iOS #8 - Чистые unit-тесты](https://reader034.vdocuments.net/reader034/viewer/2022042723/58847e821a28ab5e248b7b49/html5/thumbnails/21.jpg)
/** Метод возвращает закешированные результаты поиска для определенной поисковой строки @param searchTerm Поисковая строка @return Результаты поиска */
+ ...ServiceReturnsCachedSearchResultsForCorrectQuery ...ServiceReturnsNilWhenNoResults ...ServiceReturnsNilForInvalidCharacters ...ServiceInterpretsDashesAsUnderscores
21
![Page 22: Rambler.iOS #8 - Чистые unit-тесты](https://reader034.vdocuments.net/reader034/viewer/2022042723/58847e821a28ab5e248b7b49/html5/thumbnails/22.jpg)
Грязные тесты > Тяжело поддерживать >
Удаление тестов > Падает качество проекта
22
![Page 23: Rambler.iOS #8 - Чистые unit-тесты](https://reader034.vdocuments.net/reader034/viewer/2022042723/58847e821a28ab5e248b7b49/html5/thumbnails/23.jpg)
Грязные тесты > Тяжело поддерживать >
Удаление тестов > Падает качество проекта
23
![Page 24: Rambler.iOS #8 - Чистые unit-тесты](https://reader034.vdocuments.net/reader034/viewer/2022042723/58847e821a28ab5e248b7b49/html5/thumbnails/24.jpg)
Грязные тесты > Тяжело поддерживать >
Удаление тестов > Падает качество проекта
24
![Page 25: Rambler.iOS #8 - Чистые unit-тесты](https://reader034.vdocuments.net/reader034/viewer/2022042723/58847e821a28ab5e248b7b49/html5/thumbnails/25.jpg)
Грязные тесты > Тяжело поддерживать >
Удаление тестов > Падает качество проекта
25
![Page 26: Rambler.iOS #8 - Чистые unit-тесты](https://reader034.vdocuments.net/reader034/viewer/2022042723/58847e821a28ab5e248b7b49/html5/thumbnails/26.jpg)
Зачем нужны чистые тесты
Как писать чистые тесты
Рефакторим тест
26
![Page 27: Rambler.iOS #8 - Чистые unit-тесты](https://reader034.vdocuments.net/reader034/viewer/2022042723/58847e821a28ab5e248b7b49/html5/thumbnails/27.jpg)
Чистый тест
предметно-ориентированный язык
без лишнего контекста
тестируется одно поведение системы
27
![Page 28: Rambler.iOS #8 - Чистые unit-тесты](https://reader034.vdocuments.net/reader034/viewer/2022042723/58847e821a28ab5e248b7b49/html5/thumbnails/28.jpg)
Предметно-ориентированный язык
Хорошо XCTAssertEqualObjects(testAlbumError, expectedError); - (void)testThatServiceReturnsNilWhenNoResults [self setupStateWithBlockedUser];
Плохо XCTAssertEqualObjects(err1, err2); - (void)testNil [self setupTestData];
28
![Page 29: Rambler.iOS #8 - Чистые unit-тесты](https://reader034.vdocuments.net/reader034/viewer/2022042723/58847e821a28ab5e248b7b49/html5/thumbnails/29.jpg)
Нет лишнего контекста
Хорошо [self stubServiceCompletionBlockWithError:error]; - (void)setUp {}
Плохо ...[invocation getArgument:&result atIndex:3];...
... self.interactor.output = OCMProtocolMock(...);...
29
![Page 30: Rambler.iOS #8 - Чистые unit-тесты](https://reader034.vdocuments.net/reader034/viewer/2022042723/58847e821a28ab5e248b7b49/html5/thumbnails/30.jpg)
Тестируем одно поведениеХорошо ... XCTAssertTrue(viewReloaded); XCTAssertTrue(newDataIsShown);
Плохо ... XCTAssertTrue(newUserSaved); XCTAssertFalse(viewReloaded); XCTAssertNil([self.service obtainSearchHistory]);
30
![Page 31: Rambler.iOS #8 - Чистые unit-тесты](https://reader034.vdocuments.net/reader034/viewer/2022042723/58847e821a28ab5e248b7b49/html5/thumbnails/31.jpg)
OCMExpect([self.mockView setupInitialStateWithMenuItems:[OCMArg checkWithBlock:^BOOL(NSArray *menuItems) { __block BOOL correctSelectors = YES; [menuItems enumerateObjectsUsingBlock:^(ItemViewModel *menuItem, NSUInteger idx, BOOL stop) { NSString *expectedSelector = selectors[idx]; if (![expectedSelector isEqualToString:NSStringFromSelector(menuItem.tapSelector)]) { correctSelectors = NO; } }]; return correctSelectors && menuItems.count == selectors.count; }]]);
31
![Page 32: Rambler.iOS #8 - Чистые unit-тесты](https://reader034.vdocuments.net/reader034/viewer/2022042723/58847e821a28ab5e248b7b49/html5/thumbnails/32.jpg)
OCMExpect([self.mockView setupInitialStateWithMenuItems:[OCMArg checkWithBlock:^BOOL(NSArray *menuItems) { __block BOOL correctSelectors = YES; [menuItems enumerateObjectsUsingBlock:^(ItemViewModel *menuItem, NSUInteger idx, BOOL stop) { NSString *expectedSelector = selectors[idx]; if (![expectedSelector isEqualToString:NSStringFromSelector(menuItem.tapSelector)]) { correctSelectors = NO; } }]; return correctSelectors && menuItems.count == selectors.count; }]]);
self.mockView = [MockMenuView new]; XCTAssertTrue(self.mockView.areAllSelectorsCorrect);
32
![Page 33: Rambler.iOS #8 - Чистые unit-тесты](https://reader034.vdocuments.net/reader034/viewer/2022042723/58847e821a28ab5e248b7b49/html5/thumbnails/33.jpg)
// Случайная строка NSString *string = [[NSUUID UUID] UUIDString];
// Произвольная ошибка NSError *error = [NSError errorWithDomain:@"TestDomain" code:0 userInfo:nil];
33
![Page 34: Rambler.iOS #8 - Чистые unit-тесты](https://reader034.vdocuments.net/reader034/viewer/2022042723/58847e821a28ab5e248b7b49/html5/thumbnails/34.jpg)
// Случайная строка NSString *string = [[NSUUID UUID] UUIDString];
// Произвольная ошибка NSError *error = [NSError errorWithDomain:@"TestDomain" code:0 userInfo:nil];
NSString *string = [MockGenerator generateMockString]; NSError *error = [MockGenerator generateMockError];
34
![Page 35: Rambler.iOS #8 - Чистые unit-тесты](https://reader034.vdocuments.net/reader034/viewer/2022042723/58847e821a28ab5e248b7b49/html5/thumbnails/35.jpg)
- (void)setUp { [super setUp]; RamblerInitialAssemblyCollector *collector = [RamblerInitialAssemblyCollector new]; NSArray *assemblyClasses = [collector collectInitialAssemblyClasses]; NSMutableArray *collaboratingAssemblies = [NSMutableArray array]; for (Class assemblyClass in assemblyClasses) { if (assemblyClass == [NetworkAssembly class]) { continue; } TyphoonAssembly *assembly = [assemblyClass new]; [collaboratingAssemblies addObject:assembly]; } NetworkAssembly *networkAssembly = [NetworkAssembly new]; [networkAssembly activateWithCollaboratingAssemblies:collaboratingAssemblies]; [networkAssembly inject:self]; }
35
![Page 36: Rambler.iOS #8 - Чистые unit-тесты](https://reader034.vdocuments.net/reader034/viewer/2022042723/58847e821a28ab5e248b7b49/html5/thumbnails/36.jpg)
- (void)setUp { [super setUp]; RamblerInitialAssemblyCollector *collector = [RamblerInitialAssemblyCollector new]; NSArray *assemblyClasses = [collector collectInitialAssemblyClasses]; NSMutableArray *collaboratingAssemblies = [NSMutableArray array]; for (Class assemblyClass in assemblyClasses) { if (assemblyClass == [NetworkAssembly class]) { continue; } TyphoonAssembly *assembly = [assemblyClass new]; [collaboratingAssemblies addObject:assembly]; } NetworkAssembly *networkAssembly = [NetworkAssembly new]; [networkAssembly activateWithCollaboratingAssemblies:collaboratingAssemblies]; [networkAssembly inject:self]; [MagicalRecord setupInMemoryCoreData]; }
- (void)setUp { [self setUpWithAssemblyClass:[NetworkAssembly class]]; }
36
![Page 37: Rambler.iOS #8 - Чистые unit-тесты](https://reader034.vdocuments.net/reader034/viewer/2022042723/58847e821a28ab5e248b7b49/html5/thumbnails/37.jpg)
- (void)testThatServiceLoadsSessionProfileSuccessfully { NSError *resultError;
// большой блок логики загрузки профиля XCTAssertNil(resultError); }
- (void)testThatServiceLoadsSessionProfileWithError { NSError *expectedError = [MockObjectsFactory generateGeneralError];
// большой блок логики загрузки профиля XCTAssertEqualObjects(resultError, expectedError); }
37
![Page 38: Rambler.iOS #8 - Чистые unit-тесты](https://reader034.vdocuments.net/reader034/viewer/2022042723/58847e821a28ab5e248b7b49/html5/thumbnails/38.jpg)
- (void)testThatServiceLoadsSessionProfileSuccessfully { NSError *resultError;
// большой блок логики загрузки профиля XCTAssertNil(resultError); }
- (void)testThatServiceLoadsSessionProfileWithError { NSError *expectedError = [MockObjectsFactory generateGeneralError];
// большой блок логики загрузки профиля XCTAssertEqualObjects(resultError, expectedError); }
- (void)testThatServiceLoadsProfileSuccessfully { [self verifyThatServiceLoadsProfileWithError:nil]; }
- (void)testThatServiceLoadsProfileWithError { [self verifyThatServiceLoadsProfileWithError:error]; }
- (void)verifyThatServiceLoadsProfileWithError:(id)error { ... }
38
![Page 39: Rambler.iOS #8 - Чистые unit-тесты](https://reader034.vdocuments.net/reader034/viewer/2022042723/58847e821a28ab5e248b7b49/html5/thumbnails/39.jpg)
- (void)testThatPresenterStartsObservePost { NSString *postId = [MockObjectsFactory generateGeneralString]; [self.presenter configureCurrentModuleWithPostId:postId]; [self.presenter didTriggerViewReadyEvent]; OCMVerify([self.mockInteractor startObserveChangesWithPostId:postId]); }
39
![Page 40: Rambler.iOS #8 - Чистые unit-тесты](https://reader034.vdocuments.net/reader034/viewer/2022042723/58847e821a28ab5e248b7b49/html5/thumbnails/40.jpg)
- (void)testThatPresenterStartsObservePost { NSString *postId = [MockObjectsFactory generateGeneralString]; [self.presenter configureCurrentModuleWithPostId:postId]; [self.presenter didTriggerViewReadyEvent]; OCMVerify([self.mockInteractor startObserveChangesWithPostId:postId]); }
- (void)testThatPresenterStartsObservePost { // given NSString *postId = [MockObjectsFactory generateGeneralString]; [self.presenter configureCurrentModuleWithPostId:postId]; // when [self.presenter didTriggerViewReadyEvent]; // then OCMVerify([self.mockInteractor startObserveChangesWithPostId:postId]); }
40
![Page 41: Rambler.iOS #8 - Чистые unit-тесты](https://reader034.vdocuments.net/reader034/viewer/2022042723/58847e821a28ab5e248b7b49/html5/thumbnails/41.jpg)
Зачем нужны чистые тесты
Как писать чистые тесты
Рефакторим тест
41
![Page 42: Rambler.iOS #8 - Чистые unit-тесты](https://reader034.vdocuments.net/reader034/viewer/2022042723/58847e821a28ab5e248b7b49/html5/thumbnails/42.jpg)
OperationScheduler
queue1 queue2
NSOperation NSOperation42
![Page 43: Rambler.iOS #8 - Чистые unit-тесты](https://reader034.vdocuments.net/reader034/viewer/2022042723/58847e821a28ab5e248b7b49/html5/thumbnails/43.jpg)
NSArray *operations = self.generalQueue.operations;
for (NSOperation *generalOperation in operations) { [generalOperation addDependency:operation]; } [self.authQueue addOperation:operation];
43
![Page 44: Rambler.iOS #8 - Чистые unit-тесты](https://reader034.vdocuments.net/reader034/viewer/2022042723/58847e821a28ab5e248b7b49/html5/thumbnails/44.jpg)
• Передаем initialOperation в Планировщик
• Передаем в Планировщик 5 generalOperation
• При выполнении initialOperation создает authOperation
initial > authorization > general (5x)
44
![Page 45: Rambler.iOS #8 - Чистые unit-тесты](https://reader034.vdocuments.net/reader034/viewer/2022042723/58847e821a28ab5e248b7b49/html5/thumbnails/45.jpg)
- (void)testThatAuthOperationBlocksGeneralOperations { // given XCTestExpectation *expectation = [self expectationForCurrentTest]; NSMutableArray *operationNames = [NSMutableArray array]; NSString *const kAuthOperationName = @"AuthOperation"; NSString *const kInitialOperationName = @"InitialOperation"; NSString *const kGeneralOperationName = @"GeneralOperation"; NSUInteger const kGeneralOperationsCount = 5; __block NSNumber *operationCounter = @0; NSBlockOperation *authOperation = [NSBlockOperation blockOperationWithBlock:^{ dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ @synchronized(operationNames) { [operationNames addObject:kAuthOperationName]; } [NSThread sleepForTimeInterval:0.05]; }); }]; NSBlockOperation *initialOperation = [NSBlockOperation blockOperationWithBlock:^{ @synchronized(operationNames) { [operationNames addObject:kInitialOperationName]; } [self.scheduler addAuthOperation:authOperation]; }]; // when [self.scheduler addGeneralOperation:initialOperation]; for (NSUInteger i = 0; i < kGeneralOperationsCount; i++) { NSBlockOperation *generalOperation = [NSBlockOperation blockOperationWithBlock:^{ @synchronized(operationNames) { [operationNames addObject:kGeneralOperationName]; } @synchronized(operationCounter) { operationCounter = @([operationCounter integerValue] + 1); if ([operationCounter integerValue] == kGeneralOperationsCount) { dispatch_async(dispatch_get_main_queue(), ^{ [expectation fulfill]; }); } } }]; [self.scheduler addGeneralOperation:generalOperation]; } // then [self waitForExpectationsWithTimeout:kTestExpectationTimeout handler:^(NSError *error) { XCTAssertEqualObjects(operationNames[0], kInitialOperationName); XCTAssertEqualObjects(operationNames[1], kAuthOperationName); for (NSUInteger i = 2; i < kGeneralOperationsCount; i++) { XCTAssertEqualObjects(operationNames[i], kGeneralOperationName); } }]; }
45
![Page 46: Rambler.iOS #8 - Чистые unit-тесты](https://reader034.vdocuments.net/reader034/viewer/2022042723/58847e821a28ab5e248b7b49/html5/thumbnails/46.jpg)
XCTestExpectation *expectation = [self expectationWithDescription:@"Last operation fired"];
XCTestExpectation *expectation = [self expectationForCurrentTest];
46
![Page 47: Rambler.iOS #8 - Чистые unit-тесты](https://reader034.vdocuments.net/reader034/viewer/2022042723/58847e821a28ab5e248b7b49/html5/thumbnails/47.jpg)
NSString *const kAuthOperationName = @"AuthOperation"; NSString *const kInitialOperationName = @"InitialOperation"; NSString *const kGeneralOperationName = @"GeneralOperation";
OperationSchedulerTestConstants.h
47
![Page 48: Rambler.iOS #8 - Чистые unit-тесты](https://reader034.vdocuments.net/reader034/viewer/2022042723/58847e821a28ab5e248b7b49/html5/thumbnails/48.jpg)
NSBlockOperation *authOperation = [NSBlockOperation withBlock:^{ dispatch_async(..., ^{ @synchronized(operationNames) { [operationNames addObject:authName]; } [NSThread sleep:0.05]; }); }];
48
![Page 49: Rambler.iOS #8 - Чистые unit-тесты](https://reader034.vdocuments.net/reader034/viewer/2022042723/58847e821a28ab5e248b7b49/html5/thumbnails/49.jpg)
@interface TestBlockingByAuthOperationEnvironment : NSObject
- (void)setupWithTestCase:(XCTestCase *)testCase operationsCount:(NSUInteger)operationsCount initialBlock:(Block)initialBlock;
@property NSBlockOperation *initialOperation; @property NSBlockOperation *authOperation; @property NSArray *generalOperations; @property NSArray *firedOperationNames;
@end
49
![Page 50: Rambler.iOS #8 - Чистые unit-тесты](https://reader034.vdocuments.net/reader034/viewer/2022042723/58847e821a28ab5e248b7b49/html5/thumbnails/50.jpg)
TestBlockingByAuthOperationEnvironment *environment = [TestBlockingByAuthOperationEnvironment new];
[environment setupWithTestCase:self operationsCount:kGeneralOperationsCount initialBlock:^{
[self.scheduler addAuthOperation:environment.authOperation]; for (NSOperation *operation in environment.generalOperations) { [self.scheduler addGeneralOperation:operation]; } }];
50
![Page 51: Rambler.iOS #8 - Чистые unit-тесты](https://reader034.vdocuments.net/reader034/viewer/2022042723/58847e821a28ab5e248b7b49/html5/thumbnails/51.jpg)
- (void)testThatAuthOperationBlocksGeneralOperations { // given NSUInteger const kGeneralOperationsCount = 5; TestBlockingByAuthOperationEnvironment *environment = [TestBlockingByAuthOperationEnvironment new];
[environment setupEnvironmentWithTestCase:self generalOperationsCount:kGeneralOperationsCount initialOperationBlock:^{ [self.scheduler addAuthOperation:environment.authOperation]; for (NSOperation *operation in environment.generalOperations) { [self.scheduler addGeneralOperation:operation]; } }]; // when [self.scheduler addGeneralOperation:environment.initialOperation]; // then [self waitForExpectationsWithTimeout:kTestExpectationTimeout handler:^(NSError *error) {
[self verifyCorrectOperationOrder:kTestOrder]; }]; }
51
![Page 52: Rambler.iOS #8 - Чистые unit-тесты](https://reader034.vdocuments.net/reader034/viewer/2022042723/58847e821a28ab5e248b7b49/html5/thumbnails/52.jpg)
Предметно-ориентированный язык
Нет лишнего контекста
Тестируем одно поведение
52
![Page 53: Rambler.iOS #8 - Чистые unit-тесты](https://reader034.vdocuments.net/reader034/viewer/2022042723/58847e821a28ab5e248b7b49/html5/thumbnails/53.jpg)
What makes a clean test?
Three things.
Readability, readability,
and readability.
Егор Толстой
@igrekde