Date post: | 27-Dec-2014 |
Category: |
Technology |
Upload: | ciklum |
View: | 109 times |
Download: | 2 times |
Unit TestingSpecial Cases
How to Test
UIViewControllers
How to TestUnit Testing
What Is Testing For?
UIViewControllers Block-based code !
How to TestUnit Testing
What Is Testing For?
UIViewControllers Block-based code CoreData
How to TestUnit Testing
What Is Testing For?
UIViewController
IBOutlets & IBActions viewDidLoad dealloc UINavigationController
UIViewController
IBOutlets & IBActions
Outlet is connected
- (void)setUp { [super setUp]; sut = [ConverterViewController new]; } !- (void)tearDown { sut = nil; [super tearDown]; }
Is Outlet Connected?
- (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?
- (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?
- (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?
IBOutlets & IBActions
Outlet has a right action.
- (void)testButtonActionBinding { [sut view]; NSArray* acts = [sut.button actionsForTarget:sut forControlEvent:UIControlEventTouchUpInside]; XCTAssert([acts containsObject:@"onButton:"], @"should use correct action"); }
Is Action Connected?
IBOutlets & IBActions
The action does the right things.
viewDidLoad
Unit testing of a view controller nearly always means writing the view controller methods differently
viewDidLoad
- should call helper methods
viewDidLoad
- should call helper methods - each of the helper methods should
do just one thing (SOLID principles)
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
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)
dealloc
setUp tearDown zombie
dealloc
❓hook dealloc method of SUT when setup
dealloc
❓hook dealloc method of SUT when setup ❓record calling of the hook
dealloc
❓hook dealloc method of SUT when setup ❓record calling of the hook ❓verify if hook is called after teardown
Aspects
/// 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
dealloc
✅ hook dealloc method of SUT when setup ❓ record calling of the hook ❓ verify if hook is called after teardown
Aspects
dealloc
✅ hook dealloc method of SUT when setup ✅ record calling of the hook ❓ verify if hook is called after teardown
Aspects
instance var
dealloc
✅ hook dealloc method of SUT when setup ✅ record calling of the hook ✅ verify if hook is called after teardown
Aspects
instance var
XCTAssert
@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
@interface } @end !- ( [ !!!!!} !- ( [}
dealloc!!!!!!!!!! [sut aspect_hookSelector:NSSelectorFromString(@"dealloc") withOptions:AspectPositionBefore usingBlock:^(id<AspectInfo> aspectInfo){ _sutDeallocated = YES; } error:nil];
UINavigationController
- test push view controller - test pop view controller
UINavigationController
UINavigationController
But
UINavigationController
But
@interface UIViewController (UINavigationControllerItem) ! @property(nonatomic,readonly,retain) UINavigationController* navigationController; ! @end
-(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
Choosing not to test view controllers is the decision not to test most of your code.
UIViewController
Testing simple things is simple, and testing complex things is complex
UIViewController
Block-based code
Why does test fail?
Block-based code
! typedef void(^CompletionHandler)(NSArray * result); ! - (void)runAsyncCode:(CompletionHandler)completion { dispatch_async(dispatch_get_main_queue(), ^{ completion(nil); }); }
- (void)testRunAsyncCode { // arrange __block BOOL hasCalledBack = NO; ! // act [sut runAsyncCode:^(NSArray *result) { hasCalledBack = YES; }]; // assert XCTAssert(hasCalledBack, @"Test timed out"); }
Block-based code
- (void)testRunAsyncCode { // arrange __block BOOL hasCalledBack = NO; ! // act [sut runAsyncCode:^(NSArray *result) { hasCalledBack = YES; }]; // assert XCTAssert(hasCalledBack, @"Test timed out"); }
Block-based code
- (void)testRunAsyncCode { // arrange __block BOOL hasCalledBack = NO; ! // act [sut runAsyncCode:^(NSArray *result) { hasCalledBack = YES; }]; // assert XCTAssert(hasCalledBack, @"Test timed out"); }
Block-based code
- (void)testRunAsyncCode { // arrange __block BOOL hasCalledBack = NO; ! // act [sut runAsyncCode:^(NSArray *result) { hasCalledBack = YES; }]; // assert XCTAssert(hasCalledBack, @"Test timed out"); }
Block-based code
- (void)testRunAsyncCode { // arrange __block BOOL hasCalledBack = NO; ! // act [sut runAsyncCode:^(NSArray *result) { hasCalledBack = YES; }]; // assert XCTAssert(hasCalledBack, @"Test timed out"); }
Block-based code
- (void)testRunAsyncCode { // arrange __block BOOL hasCalledBack = NO; ! // act [sut runAsyncCode:^(NSArray *result) { hasCalledBack = YES; }]; // assert XCTAssert(hasCalledBack, @"Test timed out"); }
Block-based code
Block-based code
What can we do?
Block-based code
- (void)testRunAsyncCode { // arrange __block BOOL hasCalledBack = NO; ! // act [sut runAsyncCode:^(NSArray *result) { hasCalledBack = YES; }]; // assert XCTAssert(hasCalledBack, @"Test timed out"); }
Block-based code
- (void)testRunAsyncCode { // arrange __block BOOL hasCalledBack = NO; ! // act [sut runAsyncCode:^(NSArray *result) { hasCalledBack = YES; }]; // assert !!!! ! XCTAssert(hasCalledBack, @"Test timed out"); }
Block-based code
- (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
- (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
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
CoreData
CoreData
As long as you don't put business logic in your models, you don't have
to test them.
CoreData
creates setters & getters in run-time
CoreData
creates setters & getters in run-time
we can’t mock CoreData models
CoreData
What can we do?
CoreData- create protocol that has all model’s
properties defined
CoreData- create protocol that has all model’s
properties defined - conform NSManagedObject to the
protocol
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
CoreData
Protocol
Model<Protocol> TestModel<Protocol>
CoreDataProtocol
Model<Protocol> TestModel<Protocol>
CoreData
Have a better solution?
CoreDataCreate own CoreData stack for each
test-case
- (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
- (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”); !!!}
CoreData
Advantages?
CoreDataAdvantages?
- no additional classes
CoreDataAdvantages?
- no additional classes - no dependence on external state
CoreDataAdvantages?
- no additional classes - no dependence on external state - close approximation to the
application environment
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
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
- (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
CoreData
- test model’s additional business-logic - test ManagedObjectModel for an
entity - create and use models as a mocks
✅ UIViewControllers ✅ Block-based code ✅ CoreData
How to Test
Video is coming!
Demo Codehttps://github.com/maksumko/ConverterApp"!
maksum.ko
contact me:
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