+ All Categories
Home > Technology > Unit Testing: Special Cases

Unit Testing: Special Cases

Date post: 27-Dec-2014
Category:
Upload: ciklum
View: 109 times
Download: 2 times
Share this document with a friend
Description:
The 4th Dnepropetrovsk iOS Practice Leaders Community Meet-Up, which took place onThursday, September 25. Maxim Koshtenko, an iOS developer with 4+ years of experience in the area, held a presentation in which he told: - about the most widespread problems which appear while writing tests and how to solve them; - how to cover controllers with tests correctly and what should be visible in interface; - why tests do not work for block-based and asynchronous code and how we can fix this; - how to write tests for Core Data models; - many other useful and interesting tips and tricks. The presentation will be interesting for all iOS developers.
79
Unit Testing Special Cases
Transcript
Page 1: Unit Testing: Special Cases

Unit TestingSpecial Cases

Page 2: Unit Testing: Special Cases

How to Test

Page 3: Unit Testing: Special Cases

UIViewControllers

How to TestUnit Testing

What Is Testing For? 

Page 4: Unit Testing: Special Cases

UIViewControllers Block-based code !

How to TestUnit Testing

What Is Testing For? 

Page 5: Unit Testing: Special Cases

UIViewControllers Block-based code CoreData

How to TestUnit Testing

What Is Testing For? 

Page 6: Unit Testing: Special Cases

UIViewController

Page 7: Unit Testing: Special Cases

IBOutlets & IBActions viewDidLoad dealloc UINavigationController

UIViewController

Page 8: Unit Testing: Special Cases

IBOutlets & IBActions

Outlet is connected

Page 9: Unit Testing: Special Cases

- (void)setUp { [super setUp]; sut = [ConverterViewController new]; } !- (void)tearDown { sut = nil; [super tearDown]; }

Is Outlet Connected?

Page 10: Unit Testing: Special Cases

- (void)setUp { [super setUp]; sut = [ConverterViewController new]; } !- (void)tearDown { sut = nil; [super tearDown]; } !- (void)testThatTextFieldOutletIsConnected { XCTAssertNotNil(sut.textField, @"outlet should be connected"); }

Is Outlet Connected?

Page 11: Unit Testing: Special Cases

- (void)setUp { [super setUp]; sut = [ConverterViewController new]; } !- (void)tearDown { sut = nil; [super tearDown]; } !- (void)testThatTextFieldOutletIsConnected { XCTAssertNotNil(sut.textField, @"outlet should be connected"); }

Is Outlet Connected?

Page 12: Unit Testing: Special Cases

- (void)setUp { [super setUp]; sut = [ConverterViewController new]; } !- (void)tearDown { sut = nil; [super tearDown]; } !- (void)testThatTextFieldOutletIsConnected { [sut view]; XCTAssertNotNil(sut.textField, @"outlet should be connected"); }

Is Outlet Connected?

Page 13: Unit Testing: Special Cases

IBOutlets & IBActions

Outlet has a right action.

Page 14: Unit Testing: Special Cases

- (void)testButtonActionBinding { [sut view]; NSArray* acts = [sut.button actionsForTarget:sut forControlEvent:UIControlEventTouchUpInside]; XCTAssert([acts containsObject:@"onButton:"], @"should use correct action"); }

Is Action Connected?

Page 15: Unit Testing: Special Cases

IBOutlets & IBActions

The action does the right things.

Page 16: Unit Testing: Special Cases

viewDidLoad

Unit testing of a view controller nearly always means writing the view controller methods differently

Page 17: Unit Testing: Special Cases

viewDidLoad

- should call helper methods

Page 18: Unit Testing: Special Cases

viewDidLoad

- should call helper methods - each of the helper methods should

do just one thing (SOLID principles)

Page 19: Unit Testing: Special Cases

viewDidLoad

- should call helper methods - each of the helper methods should

do just one thing (SOLID principles) - write tests for each of the helper

methods

Page 20: Unit Testing: Special Cases

viewDidLoad

- should call helper methods - each of the helper methods should

do just one thing (SOLID principles) - write tests for each of the helper

methods - test viewDidLoad calls helper

methods (partial mock)

Page 21: Unit Testing: Special Cases

dealloc

setUp tearDown zombie

Page 22: Unit Testing: Special Cases

dealloc

❓hook dealloc method of SUT when setup

Page 23: Unit Testing: Special Cases

dealloc

❓hook dealloc method of SUT when setup ❓record calling of the hook

Page 24: Unit Testing: Special Cases

dealloc

❓hook dealloc method of SUT when setup ❓record calling of the hook ❓verify if hook is called after teardown

Page 25: Unit Testing: Special Cases

Aspects

Page 26: Unit Testing: Special Cases

/// Adds a block of code before/instead/after the current `selector` for a specific instance. - (id<AspectToken>)aspect_hookSelector:(SEL)selector withOptions:(AspectOptions)options usingBlock:(id)block error:(NSError **)error; !/// Called after the original implementation (default) AspectPositionAfter, !/// Will replace the original implementation. AspectPositionInstead, !/// Called before the original implementation. AspectPositionBefore,

Aspects

Page 27: Unit Testing: Special Cases

dealloc

✅ hook dealloc method of SUT when setup ❓ record calling of the hook ❓ verify if hook is called after teardown

Aspects

Page 28: Unit Testing: Special Cases

dealloc

✅ hook dealloc method of SUT when setup ✅ record calling of the hook ❓ verify if hook is called after teardown

Aspects

instance var

Page 29: Unit Testing: Special Cases

dealloc

✅ hook dealloc method of SUT when setup ✅ record calling of the hook ✅ verify if hook is called after teardown

Aspects

instance var

XCTAssert

Page 30: Unit Testing: Special Cases

@interface ConverterViewControllerTests : XCTestCase { ConverterViewController* sut; BOOL _sutDeallocated; } @end !- (void)setUp { [super setUp]; sut = [ConverterViewController new]; _sutDeallocated = NO; [sut aspect_hookSelector:NSSelectorFromString(@"dealloc") withOptions:AspectPositionBefore usingBlock:^(id<AspectInfo> aspectInfo){ _sutDeallocated = YES; } error:nil]; } !- (void)tearDown { sut = nil; XCTAssertTrue(_sutDeallocated, @"SUT is not deallocated"); [super tearDown]; }

dealloc

Page 31: Unit Testing: Special Cases

@interface } @end !- ( [ !!!!!} !- ( [}

dealloc!!!!!!!!!! [sut aspect_hookSelector:NSSelectorFromString(@"dealloc") withOptions:AspectPositionBefore usingBlock:^(id<AspectInfo> aspectInfo){ _sutDeallocated = YES; } error:nil];

Page 32: Unit Testing: Special Cases

UINavigationController

- test push view controller - test pop view controller

Page 33: Unit Testing: Special Cases

UINavigationController

Page 34: Unit Testing: Special Cases

UINavigationController

But

Page 35: Unit Testing: Special Cases

UINavigationController

But

@interface UIViewController (UINavigationControllerItem) ! @property(nonatomic,readonly,retain) UINavigationController* navigationController; ! @end

Page 36: Unit Testing: Special Cases

-(void)testTappingDetailsShouldDisplayDetails { UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:sut]; id mockNav = [OCMockObject partialMockForObject:nav]; [[mockNav expect] pushViewController:[OCMArg any] animated:YES]; [sut onShowDetailsButton:nil]; [mockNav verify]; }

UINavigationController

Page 37: Unit Testing: Special Cases

Choosing not to test view controllers is the decision not to test most of your code.

UIViewController

Page 38: Unit Testing: Special Cases

Testing simple things is simple, and testing complex things is complex

UIViewController

Page 39: Unit Testing: Special Cases

Block-based code

Why does test fail?

Page 40: Unit Testing: Special Cases

Block-based code

! typedef void(^CompletionHandler)(NSArray * result); ! - (void)runAsyncCode:(CompletionHandler)completion { dispatch_async(dispatch_get_main_queue(), ^{ completion(nil); }); }

Page 41: Unit Testing: Special Cases

- (void)testRunAsyncCode { // arrange __block BOOL hasCalledBack = NO; ! // act [sut runAsyncCode:^(NSArray *result) { hasCalledBack = YES; }]; // assert XCTAssert(hasCalledBack, @"Test timed out"); }

Block-based code

Page 42: Unit Testing: Special Cases

- (void)testRunAsyncCode { // arrange __block BOOL hasCalledBack = NO; ! // act [sut runAsyncCode:^(NSArray *result) { hasCalledBack = YES; }]; // assert XCTAssert(hasCalledBack, @"Test timed out"); }

Block-based code

Page 43: Unit Testing: Special Cases

- (void)testRunAsyncCode { // arrange __block BOOL hasCalledBack = NO; ! // act [sut runAsyncCode:^(NSArray *result) { hasCalledBack = YES; }]; // assert XCTAssert(hasCalledBack, @"Test timed out"); }

Block-based code

Page 44: Unit Testing: Special Cases

- (void)testRunAsyncCode { // arrange __block BOOL hasCalledBack = NO; ! // act [sut runAsyncCode:^(NSArray *result) { hasCalledBack = YES; }]; // assert XCTAssert(hasCalledBack, @"Test timed out"); }

Block-based code

Page 45: Unit Testing: Special Cases

- (void)testRunAsyncCode { // arrange __block BOOL hasCalledBack = NO; ! // act [sut runAsyncCode:^(NSArray *result) { hasCalledBack = YES; }]; // assert XCTAssert(hasCalledBack, @"Test timed out"); }

Block-based code

Page 46: Unit Testing: Special Cases

- (void)testRunAsyncCode { // arrange __block BOOL hasCalledBack = NO; ! // act [sut runAsyncCode:^(NSArray *result) { hasCalledBack = YES; }]; // assert XCTAssert(hasCalledBack, @"Test timed out"); }

Block-based code

Page 47: Unit Testing: Special Cases

Block-based code

What can we do?

Page 48: Unit Testing: Special Cases

Block-based code

Page 49: Unit Testing: Special Cases

- (void)testRunAsyncCode { // arrange __block BOOL hasCalledBack = NO; ! // act [sut runAsyncCode:^(NSArray *result) { hasCalledBack = YES; }]; // assert XCTAssert(hasCalledBack, @"Test timed out"); }

Block-based code

Page 50: Unit Testing: Special Cases

- (void)testRunAsyncCode { // arrange __block BOOL hasCalledBack = NO; ! // act [sut runAsyncCode:^(NSArray *result) { hasCalledBack = YES; }]; // assert !!!! ! XCTAssert(hasCalledBack, @"Test timed out"); }

Block-based code

Page 51: Unit Testing: Special Cases

- (void)testRunAsyncCode { // arrange __block BOOL hasCalledBack = NO; ! // act [sut runAsyncCode:^(NSArray *result) { hasCalledBack = YES; }]; // assert NSDate *timeout = [NSDate dateWithTimeIntervalSinceNow:0.1]; while([timeout timeIntervalSinceNow] > 0 && hasCalledBack == NO) { [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:timeout]; } XCTAssert(hasCalledBack, @"Test timed out"); }

Block-based code

Page 52: Unit Testing: Special Cases

- (void)testRunAsyncCode { // arrange __block BOOL hasCalledBack = NO; ! // act [sut runAsyncCode:^(NSArray *result) { hasCalledBack = YES; }]; // assert NSDate *timeout = [NSDate dateWithTimeIntervalSinceNow:0.1]; while([timeout timeIntervalSinceNow] > 0 && hasCalledBack == NO) { [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:timeout]; } XCTAssert(hasCalledBack, @"Test timed out"); }

Block-based code

Page 53: Unit Testing: Special Cases

Block-based codeAlso we can use:

- (void)verifyWithDelay:(NSTimeInterval)delay { NSTimeInterval step = 0.01; while(delay > 0) { if([expectations count] == 0) break; NSDate* until = [NSDate dateWithTimeIntervalSinceNow:step]; [[NSRunLoop currentRunLoop] runUntilDate:until]; delay -= step; step *= 2; } [self verify]; }

OCMock

Page 54: Unit Testing: Special Cases

CoreData

Page 55: Unit Testing: Special Cases

CoreData

As long as you don't put business logic in your models, you don't have

to test them.

Page 56: Unit Testing: Special Cases

CoreData

creates setters & getters in run-time

Page 57: Unit Testing: Special Cases

CoreData

creates setters & getters in run-time

we can’t mock CoreData models

Page 58: Unit Testing: Special Cases

CoreData

What can we do?

Page 59: Unit Testing: Special Cases

CoreData- create protocol that has all model’s

properties defined

Page 60: Unit Testing: Special Cases

CoreData- create protocol that has all model’s

properties defined - conform NSManagedObject to the

protocol

Page 61: Unit Testing: Special Cases

CoreData- create protocol that has all model’s

properties defined - conform NSManagedObject to the

protocol - create NSObject model just for

testing, conforms to the protocol and @synthesize properties

Page 62: Unit Testing: Special Cases

CoreData

Protocol

Model<Protocol> TestModel<Protocol>

Page 63: Unit Testing: Special Cases

CoreDataProtocol

Model<Protocol> TestModel<Protocol>

Page 64: Unit Testing: Special Cases

CoreData

Have a better solution?

Page 65: Unit Testing: Special Cases

CoreDataCreate own CoreData stack for each

test-case

Page 66: Unit Testing: Special Cases

- (void)setUp { [super setUp]; NSURL *modelURL = [[NSBundle mainBundle] URLForResource:@"SimpleInvoice" withExtension:@“momd"]; ! NSManagedObjectModel *mom = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL]; ! NSPersistentStoreCoordinator *psc = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:mom]; ! XCTAssertNotNil([psc addPersistentStoreWithType:NSInMemoryStoreType configuration:nil URL:nil options:nil error:NULL], @"Should be able to add in-memory store”); ! _moc = [[NSManagedObjectContext alloc] init]; _moc.persistentStoreCoordinator = psc; }

CoreData

Page 67: Unit Testing: Special Cases

- (void{ [ ! initWithContentsOfURL! initWithManagedObjectModel!!!!!!!! }

CoreData

- (void)setUp { !!!!!!!!!! XCTAssertNotNil([psc addPersistentStoreWithType:NSInMemoryStoreType configuration:nil URL:nil options:nil error:NULL], @"Should be able to add in-memory store”); !!!}

Page 68: Unit Testing: Special Cases

CoreData

Advantages?

Page 69: Unit Testing: Special Cases

CoreDataAdvantages?

- no additional classes

Page 70: Unit Testing: Special Cases

CoreDataAdvantages?

- no additional classes - no dependence on external state

Page 71: Unit Testing: Special Cases

CoreDataAdvantages?

- no additional classes - no dependence on external state - close approximation to the

application environment

Page 72: Unit Testing: Special Cases

CoreDataAdvantages?

- no additional classes - no dependence on external state - close approximation to the

application environment - we are able to create base test

class with a stack and subclass it where we need

Page 73: Unit Testing: Special Cases

CoreDataAdvantages?

- no additional classes

- no dependence on external state

- close approximation to the application environment

- we are able to create base test class with a stack and subclass it where we need

Page 74: Unit Testing: Special Cases

- (void)testFullNameReturnsСorrectString { Person* ps; ps = [NSEntityDescription insertNewObjectForEntityForName:@"Person" inManagedObjectContext:_moc]; ps.firstName = @"Tom"; ps.lastName = @“Lol"; ! STAssertTrue([[ps fullName] isEqualToString:@"Lol Tom"], @"should have matched full name"); }

CoreData

Page 75: Unit Testing: Special Cases

CoreData

- test model’s additional business-logic - test ManagedObjectModel for an

entity - create and use models as a mocks

Page 76: Unit Testing: Special Cases

✅ UIViewControllers ✅ Block-based code ✅ CoreData

How to Test

Page 77: Unit Testing: Special Cases

Video is coming!

Demo Codehttps://github.com/maksumko/ConverterApp"!

maksum.ko

contact me:

Page 78: Unit Testing: Special Cases

Sourceshttp://www.amazon.com/Test-Driven-iOS-Development-Developers-Library/dp/0321774183 !http://www.objc.io/issue-1/testing-view-controllers.html http://www.silverbaytech.com/2013/02/25/ios-testing-part-3-testing-view-controller/ http://iosunittesting.com/unit-testing-view-controllers/ http://blog.carbonfive.com/2010/03/10/testing-view-controllers/ !http://iosunittesting.com/how-to-unit-test-completion-blocks/ !http://iosunittesting.com/unit-testing-core-data/ http://ashfurrow.com/blog/unit-testing-with-core-data-models http://www.sicpers.info/2010/06/template-class-for-unit-testing-core-data-entities/ http://iamleeg.blogspot.com/2010/01/unit-testing-core-data-driven-apps-fit.html

Page 79: Unit Testing: Special Cases

Recommended