Upgrade to Pro — share decks privately, control downloads, hide ads and more …

iOS Unit Testing - UAMobile

iOS Unit Testing - UAMobile

Avatar for Maksym Koshtenko

Maksym Koshtenko

November 22, 2014

More Decks by Maksym Koshtenko

Other Decks in Programming

Transcript

  1. What is Testing For? What is Unit Test? Verifications XCTest

    Dependency Injection Mocks XCTest Xcode 6 API dealloc Unit Testing Unit Testing What Is Testing For?
  2. What is Unit Test? Unit tests are small pieces of

    code that test the behaviour of other code.
  3. A test is not a unit test if: • It

    talks to the database • It communicates across the network • It touches the file system • It can't run at the same time as any of your other unit tests • You have to do special things to your environment (such as editing config files) to run it.
  4. A good unit test is: • Able to be fully

    automated • Tests a single logical concept in the system • Consistently returns the same result • Maintainable and order-independent • Independent • Runs fast • Readable • Trustworthy
  5. Return Value Verification We inspect the value returned from the

    system under test and compare it to the expected state. SUT Setup Exercise Verify Tear Down Behaviour (Indirect Outputs) DOC
  6. State Verification We inspect the state of the system under

    test after it has been exercised and compare it to the expected state. SUT Setup Exercise Verify Tear Down Behaviour (Indirect Outputs) DOC State
  7. SUT Behaviour Verification We capture the indirect outputs of the

    SUT as they occur and compare them to the expected behaviour. Setup Exercise Verify Tear Down Behaviour (Indirect Outputs) DOC
  8. SUT Behaviour Verification We capture the indirect outputs of the

    SUT as they occur and compare them to the expected behaviour. Setup Exercise Verify Tear Down Behaviour (Indirect Outputs) Fake Verify
  9. XCTest • Provided by Xcode • New iOS application projects

    automatically include a unit testing target
  10. XCTest • Provided by Xcode • New iOS application projects

    automatically include a unit testing target • Test classes do not have a header file
  11. XCTest • Provided by Xcode • New iOS application projects

    automatically include a unit testing target • Test classes do not have a header file • It’s not necessary to add application’s source files to the XCTest Target
  12. XCTest • Provided by Xcode • New iOS application projects

    automatically include a unit testing target • Test classes do not have a header file • It’s not necessary to add application’s source files to the XCTest Target • Each test case is a method with prefix test
  13. XCTest • Provided by Xcode • New iOS application projects

    automatically include a unit testing target • Test classes do not have a header file • It’s not necessary to add application’s source files to the XCTest Target • Each test case is a method with prefix test • Since Xcode 5 allows to run tests from the editor
  14. XCTest Flow Set Up Tear Down Test - (void)setUp {

    [super setUp]; // This method is called before the invocation of each test method in the class. } - (void)tearDown { // This method is called after the invocation of each test method in the class. [super tearDown]; } - (void)testExample { XCTFail(@"No implementation for \"%s\"", __PRETTY_FUNCTION__); }
  15. @interface ConverterModelTests : XCTestCase { ConverterModel* sut; } @end @implementation

    ConverterModelTests - (void)setUp { [super setUp]; sut = [ConverterModel new]; } - (void)tearDown { sut = nil; [super tearDown]; } - (void)testMakeConversionWithModeMilesToKilometers { // Arrange sut.value = @(1); // Act [sut makeConversionWithMode:ConverterModeMilesToKm]; // Assert XCTAssertTrue([sut.text isEqualToString:@"1.609344"], @"should convert"); } @end
  16. - (void)viewDidLoad { [super viewDidLoad]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(someAction:) name:kSomeNotification

    object:nil]; } - (void)testViewDidLoadAddsObserverForSomeNotification { // Arrange // Act // Assert }
  17. Dependency Injection is a software design pattern that implements passing

    a service to a client, rather than allowing a client to build or find the service.
  18. Constructor Injection the dependencies are provided through a class constructor

    - (id)initWithNotificationCenter:(NSNotificationCenter *)notificationCenter
  19. @property (nonatomic, weak) NSUserDefaults* userDefaults; Setter Injection the client exposes

    a setter method that the injector uses to inject the dependency
  20. - (void)viewDidLoad { [super viewDidLoad]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(someAction:) name:kSomeNotification

    object:nil]; } Dependency - (void)testViewDidLoadAddsObserverForSomeNotification { // Arrange // Act // Assert }
  21. - (id)initWithNotificationCenter:(NSNotificationCenter *)notificationCenter { self = [super init]; if(self) {

    _notificationCenter = notificationCenter; } return self; } - (void)viewDidLoad { [super viewDidLoad]; [_notificationCenter addObserver:self selector:@selector(someAction:) name:kSomeNotification object:nil]; } Inject Dependency
  22. is a fake object in the system that verifies the

    system under test interacted as expected with the fake object. Mock Object
  23. It is common in unit tests to mock or stub

    collaborators of the system under test so that the test is independent of the implementation of the collaborators. Mock Object Behaviour Verification SUT Setup Exercise Verify Tear Down Behaviour (Indirect Outputs) DOC
  24. It is common in unit tests to mock or stub

    collaborators of the system under test so that the test is independent of the implementation of the collaborators. Mock Object Behaviour Verification SUT Setup Exercise Verify Tear Down Behaviour (Indirect Outputs) Fake
  25. It is common in unit tests to mock or stub

    collaborators of the system under test so that the test is independent of the implementation of the collaborators. Mock Object SUT Behaviour Verification Setup Exercise Verify Tear Down Behaviour (Indirect Outputs) Fake Verify
  26. Test Mocks SUT communicates with the mock object, and all

    communication is recorded in the mock. The test uses the mock to verify that the test passes. SUT Mock Communicate Assert
  27. Test Stubs Returns specified result for a message, stubs can’t

    fail the test. SUT Stub Communicate Assert
  28. @interface ConverterViewController : UIViewController @property (nonatomic, weak) NSNotificationCenter* notificationCenter; @end

    @implementation ConverterViewController - (void)viewDidLoad { [super viewDidLoad]; [_notificationCenter addObserver:self selector:@selector(someAction:) name:kSomeNotification object:nil]; } @end Injected Dependency
  29. @interface ConverterViewController : UIViewController @property (nonatomic, weak) NSNotificationCenter* notificationCenter; @end

    @implementation ConverterViewController - (void)viewDidLoad { [super viewDidLoad]; [_notificationCenter addObserver:self selector:@selector(someAction:) name:kSomeNotification object:nil]; } @end Injected Dependency Replace with a mock
  30. OCMock Provides •stub objects that return pre-determined values for specific

    method invocations •dynamic mocks that can be used to verify interaction patterns •partial mocks to overwrite selected methods of existing objects
  31. Mocks // Creates a mock object that can be used

    as if it were an instance of // SomeClass. id mock = [OCMockObject mockForClass:[SomeClass class]]; // Tells the mock object that someMethod: should be called with an argument // that is equal to someArgument. [[mock expect] someMethod:someArgument]; // After this setup the functionality under test should be invoked // followed by [mock verify]; // When a method is called on a mock object that has not been set up with // either expect or stub the mock object will raise an exception. This // fail-fast mode can be turned off by creating a "nice" mock: id mock = [OCMockObject niceMockForClass:[SomeClass class]]; // While nice mocks will simply ignore all unexpected methods it is // possible to disallow specific methods: [[mock reject] someMethod];
  32. Stubs // Tells the mock object that when someMethod: is

    called with // someArgument it should return aValue. [[[mock stub] andReturn:aValue] someMethod:someArgument]; // It is not possible to pass primitive types directly. [[[mock stub] andReturnValue:@YES] aMethodReturnABoolean:someArgument]; // The mock object can also throw an exception or post a notification // when a method is called [[[mock stub] andThrow:anException] someMethod:someArgument]; [[[mock stub] andPost:aNotification] someMethod:someArgument]; // If Objective-C blocks are available a block can be used to handle the // invocation and set up a return value void (^theBlock)(NSInvocation *) = ^(NSInvocation *invocation) { /* code that reads and modifies the invocation object */ }; [[[mock stub] andDo:theBlock] someMethod:[OCMArg any]];
  33. @interface ConverterViewController : UIViewController @property (nonatomic, weak) NSNotificationCenter* notificationCenter; @end

    @implementation ConverterViewController - (void)viewDidLoad { [super viewDidLoad]; [_notificationCenter addObserver:self selector:@selector(someAction:) name:kSomeNotification object:nil]; } @end Injected Dependency
  34. - (void)testViewDidLoadAddsObserver { // Arrange id mock = [OCMockObject mockForClass:[NSNotificationCenter

    class]]; [[mock expect] addObserver:sut selector:[OCMArg anySelector] name:kConverterModelDidUpdateNotification object:OCMOCK_ANY]; sut.notificationCenter = mock; // Act [sut viewDidLoad]; // Assert [mock verify]; } How we test this with OCMock
  35. - (void)testRunAsyncCode { __block BOOL hasCalledBack = NO; [sut runAsyncCode:^(NSArray

    *result) { XCTAssertNotNil(result); hasCalledBack = YES; }]; NSDate *timeout = [NSDate dateWithTimeIntervalSinceNow:0.1]; while([timeout timeIntervalSinceNow] > 0 && hasCalledBack == NO) { [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:timeout]; } XCTAssert(hasCalledBack, @"Test timed out"); Writing Tests of Asynchronous Operations }
  36. - (void __block [sut hasCalledBack = }]; NSDate while [[

    } XCTAssert Writing Tests of Asynchronous Operations - (void)testRunAsyncCode { [sut runAsyncCode:^(NSArray *result) { XCTAssertNotNil(result); }]; }
  37. - (void)testPerformanceExample { // This is an example of a

    performance test case. [self measureBlock:^{ // Put the code you want to measure the time of here. }]; } Benchmark the Performance of Code
  38. - (void)testPerformanceExample { NSString* fileName = [[NSBundle mainBundle] pathForResource:@"data" ofType:@"json"];

    NSData* data = [NSData dataWithContentsOfFile:fileName]; [self measureBlock:^{ [sut precessData:data]; }]; } Benchmark the Performance of Code
  39. - (void)testPerformanceExample { NSString* fileName = [[NSBundle mainBundle] pathForResource:@"data" ofType:@"json"];

    NSData* data = [NSData dataWithContentsOfFile:fileName]; [self measureBlock:^{ [sut precessData:data]; }]; } Benchmark the Performance of Code [self measureBlock:^{ [sut precessData:data]; }];
  40. [self measureBlock:^{ [sut precessData:data]; }]; - (void)testPerformanceExample { NSString* fileName

    = [[NSBundle mainBundle] pathForResource:@"data" ofType:@"json"]; NSData* data = [NSData dataWithContentsOfFile:fileName]; [self measureBlock:^{ [sut precessData:data]; }]; } Benchmark the Performance of Code
  41. [self measureBlock:^{ [sut precessData:data]; }]; - (void)testPerformanceExample { NSString* fileName

    = [[NSBundle mainBundle] pathForResource:@"data" ofType:@"json"]; NSData* data = [NSData dataWithContentsOfFile:fileName]; [self measureBlock:^{ [sut precessData:data]; }]; } Benchmark the Performance of Code
  42. [self measureBlock:^{ [sut precessData:data]; }]; - (void)testPerformanceExample { NSString* fileName

    = [[NSBundle mainBundle] pathForResource:@"data" ofType:@"json"]; NSData* data = [NSData dataWithContentsOfFile:fileName]; [self measureBlock:^{ [sut precessData:data]; }]; } Benchmark the Performance of Code
  43. dealloc • hook dealloc method of SUT when setup •

    record calling of the hook • verify if hook is called after teardown
  44. /// 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
  45. dealloc ✓ hook dealloc method of SUT when setup •

    record calling of the hook • verify if hook is called after teardown Aspects
  46. dealloc ✓ hook dealloc method of SUT when setup ✓

    record calling of the hook • verify if hook is called after teardown Aspects instance var
  47. dealloc ✓ hook dealloc method of SUT when setup ✓

    record calling of the hook ✓ verify if hook is called after teardown Aspects instance var XCTAssert
  48. @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
  49. @interface } @end - ( [ } - ( [

    } dealloc [sut aspect_hookSelector:NSSelectorFromString(@"dealloc") withOptions:AspectPositionBefore usingBlock:^(id<AspectInfo> aspectInfo){ _sutDeallocated = YES; } error:nil];
  50. What is Testing For? What is Unit Test? Verifications XCTest

    Dependency Injection Mocks XCTest Xcode 6 API dealloc Unit Testing
  51. What is Testing For? What is Unit Test? Verifications XCTest

    Dependency Injection Mocks XCTest Xcode 6 API dealloc Unit Testing Demo Code https:/ /github.com/maksumko/ConverterApp" ! maksum.ko contact me: [email protected]