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

iOS Unit Testing - Intro

iOS Unit Testing - Intro

iOS Practice Leaders Community Meet-up in Dnipropetrovsk

Avatar for Maksym Koshtenko

Maksym Koshtenko

June 19, 2014

More Decks by Maksym Koshtenko

Other Decks in Programming

Transcript

  1. Cost of Bugs Time Detected Time Introduced Requirements Architecture Coding

    System Test Post-" Release Requirements 1 3 5-10 10 10-100 Architecture - 1 10 15 25-100 Coding - - 1 10 10-25 Cost of Fixing Bugs Found at Different Stages of the Software Development Process
  2. What is Unit Test? Unit tests are small pieces of

    code that test the behavior 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: Consistently returns the same result (no

    random numbers, save those for integration tests)
  5. A good unit test: is Trustworthy (when you see its

    result, you don’t need to debug the code just to be sure)
  6. 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
  7. 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 Behavior " (Indirect" Outputs) DOC
  8. 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 Behavior " (Indirect" Outputs) DOC State
  9. SUT Behavior Verification We capture the indirect outputs of the

    SUT as they occur and compare them to the expected behavior. Setup Exercise Verify Tear Down Behavior " (Indirect" Outputs) DOC
  10. SUT Behavior Verification We capture the indirect outputs of the

    SUT as they occur and compare them to the expected behavior. Setup Exercise Verify Tear Down Behavior " (Indirect" Outputs) Fake Verify
  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" • Each test case is a method with prefix test" • Xcode 5 allows to run tests from the editor
  12. XCTest Flow Setup 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__); }
  13. Test main actions: • Arrange objects, creating and setting them

    up as necessary. " • Act on an object. " • Assert that something is as expected. - (void)testMakeConversionWithModeMilesToKilometers { // Arrange sut.value = @(1); // Act [sut makeConversionWithMode:ConverterModeMilesToKm]; // Assert XCTAssertTrue([sut.text isEqualToString:@"1.609344"], @"should convert from miles to kilometers"); }
  14. @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 from miles to kilometers"); } @end
  15. XCTest Asserts • XCTFail (format…) • XCTAssertNil (a1, format…) •

    XCTAssertNotNil (a1, format…) • XCTAssert (a1, format…) • XCTAssertTrue (a1, format…) • XCTAssertFalse (a1, format…) • XCTAssertEqualObjects (a1, a2, format…) • XCTAssertEquals (a1, a2, format…) • XCTAssertEqualsWithAccuracy (a1, a2, accuracy, format…) • XCTAssertThrows (expression, format…) • XCTAssertThrowsSpecific (expression, exception, format…) • XCTAssertThrowsSpecificNamed (expression, exception, name, format…) • XCTAssertNoThrow (expression, format…) • XCTAssertNoThrowSpecific (expression, exception, format…) • XCTAssertNoThrowSpecificNamed (expression, exception, name, format…)
  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. Setter Injection the client exposes a setter method that the

    injector uses to inject the dependency @property (nonatomic, weak) NSUserDefaults* userDefaults;
  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

    object 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 SUT Behavior Verification Setup Exercise Verify Tear Down Behavior " (Indirect" Outputs) Fake Verify
  24. 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
  25. Test Stubs Returns specified result for a message, stubs can’t

    fail the test. SUT Stub Communicate Assert
  26. @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
  27. @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
  28. What we need? •an object that responds to addObserver:selector:name:object:! •possibility

    to record a call" •check for notification’s name" •verification
  29. @interface FakeNotificationCenter : NSObject { BOOL _hasCalled; } @property (nonatomic,

    strong) NSString* expectedName; - (void)addObserver:(id)observer selector:(SEL)aSelector name:(NSString *)aName object:(id)anObject; - (void)verify; @end @implementation FakeNotificationCenter - (void)addObserver:(id)observer selector:(SEL)aSelector name:(NSString *)aName object:(id)anObject { _hasCalled = [_expectedName isEqualToString:aName]; } - (void)verify { NSAssert(_hasCalled, @"expected method was not called with specified parameters"); } @end What we can do
  30. - (void)testViewDidLoadAddsObserverForSomeNotification { // Arrange FakeNotificationCenter* fake = [FakeNotificationCenter new];

    fake.expectedName = kSomeNotification; sut.notificationCenter = (id)fake; // Assert [fake verify]; } How can we use it?
  31. - (void)testViewDidLoadAddsObserverForSomeNotification { // Arrange FakeNotificationCenter* fake = [FakeNotificationCenter new];

    fake.expectedName = kSomeNotification; sut.notificationCenter = (id)fake; // Assert [fake verify]; } -[ConverterViewControllerTests testViewDidLoadAddsObserver] failed: expected method was not called with specified parameters How can we use it?
  32. - (void)testViewDidLoadAddsObserverForSomeNotification { // Arrange FakeNotificationCenter* fake = [FakeNotificationCenter new];

    fake.expectedName = kSomeNotification; sut.notificationCenter = (id)fake; // Act [sut viewDidLoad]; // Assert [fake verify]; } How can we use it?
  33. 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
  34. 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];
  35. 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]];
  36. @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
  37. - (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
  38. How to start with Existing Project • All developers in

    team must write and run tests" • Start with warnings and bugs
  39. How to start with Existing Project • All developers in

    team must write and run tests" • Start with warnings and bugs! • Isolate modules and classes
  40. How to start with Existing Project • All developers in

    team must write and run tests" • Start with warnings and bugs! • Isolate modules and classes! • Don’t miss refactoring
  41. How to start with Existing Project • All developers in

    team must write and run tests" • Start with warnings and bugs! • Isolate modules and classes! • Don’t miss refactoring! • Increase test-coverage step-by-step with new functionality