unit tesing in ios

Post on 06-May-2015

563 Views

Category:

Software

3 Downloads

Preview:

Click to see full reader

DESCRIPTION

iOS Practice Leaders Community Meet-up. “Unit Testing in iOS” by Maxim Koshtenko - why we need tests and what their use in applications’ developing on a project is; - how one should and should not test source code; - review of some of the most popular tools which make test-writing easier; - how to switch to unit-testing on a project which already exists.

TRANSCRIPT

Unit Testing

Unit Testing

What Is Testing For? 

Unit Testing

When Should Software Be Tested?

What Is Testing For? 

What Is Testing For?

What Is Testing For?

Testing can show that the product works!

When Should Software Be Tested?

Waterfall project management process

Waterfall project management process

Requirements

Waterfall project management process

Requirements

Specification

Waterfall project management process

Requirements

Development

Specification

Waterfall project management process

Requirements

Test

Development

Specification

Waterfall project management process

Requirements

Test

Development

Specification

Deployment

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

When Should Software Be Tested?

When Should Software Be Tested?

Software should be tested all the time!

Unit Testing

What is Unit Test?

Unit tests are small pieces of code that test the behavior of other code.

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.

Properties of a Good Unit Test

A good unit test:

is able to be fully automated

A good unit test:

Tests a single logical concept in the system

A good unit test:

Consistently returns the same result (no random numbers, save those for integration

tests)

A good unit test:

is Maintainable and order-independent

A good unit test:

is Independent

A good unit test:

Runs fast

A good unit test:

is Readable

A good unit test:

is Trustworthy (when you see its result, you don’t need to debug the code just to be sure)

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

Verifications

Types of Verifications

•Return Value

•State

•Behavior

Types of Verifications

SUT

Types of Verifications

SUTBehavior (Indirect Outputs)

DOC

Types of Verifications

SUTSetup

Exercise

Verify

Tear Down

Behavior (Indirect Outputs)

DOC

Return Value Verification

We inspect the value returned from the system under test and compare it to the

expected state.

SUTSetup

Exercise

Verify

Tear Down

Behavior (Indirect Outputs)

DOC

State Verification

We inspect the state of the system under test after it has been exercised and compare

it to the expected state.

SUTSetup

Exercise

Verify

Tear Down

Behavior (Indirect Outputs)

DOC

State

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

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

XCTest

XCTest• Provided by Xcode

XCTest• Provided by Xcode

• New iOS application projects automatically include a unit testing target

XCTest• Provided by Xcode

• New iOS application projects automatically include a unit testing target

• Test classes do not have a header file

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

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

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

XCTest Flow

Setup Tear DownTest

- (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__); }

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"); }

@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

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…)

- (void)viewDidLoad { [super viewDidLoad]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(someAction:) name:kSomeNotification object:nil]; }

- (void)viewDidLoad { [super viewDidLoad]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(someAction:) name:kSomeNotification object:nil]; }

- (void)testViewDidLoadAddsObserverForSomeNotification { // Arrange // Act // Assert !}

Dependency Injection

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.

Constructor Injection

the dependencies are provided through a class constructor

- (id)initWithNotificationCenter:(NSNotificationCenter *)notificationCenter

Setter Injection

the client exposes a setter method that the injector uses to inject the dependency

@property (nonatomic, weak) NSUserDefaults* userDefaults;

Dependency Injection

providing an instance variable for dependency

- (void)viewDidLoad { [super viewDidLoad]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(someAction:) name:kSomeNotification object:nil]; }

Dependency

- (void)testViewDidLoadAddsObserverForSomeNotification { // Arrange // Act // Assert !}

- (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

Mocks

is a fake object in the system that verifies the object under test interacted as expected with the fake object.

Mock Object

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

SUTBehavior Verification

Setup

Exercise

Verify

Tear Down

Behavior (Indirect Outputs)

Fake

Verify

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 MockCommunicate

Assert

Test

Stubs

Returns specified result for a message, stubs can’t fail the test.

SUT StubCommunicate

Assert

@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

@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

What we need?

•an object that responds to addObserver:selector:name:object:

•possibility to record a call

•check for notification’s name

•verification

@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

- (void)testViewDidLoadAddsObserverForSomeNotification { // Arrange FakeNotificationCenter* fake = [FakeNotificationCenter new]; fake.expectedName = kSomeNotification; sut.notificationCenter = (id)fake; // Assert [fake verify]; } !

How can we use it?

- (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?

- (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?

OCMock

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

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];

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]];

@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

- (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

Legacy Code?

How to start with Existing Project

• All developers in team must write and run tests

How to start with Existing Project

• All developers in team must write and run tests

• Start with warnings and bugs

How to start with Existing Project

• All developers in team must write and run tests

• Start with warnings and bugs

• Isolate modules and classes

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

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

Demo Code

https://github.com/maksumko/ConverterApp

!

maksum.ko

Sources

http://www.amazon.com/Art-Unit-Testing-Examples-Net/dp/1933988274

!

http://www.amazon.com/Test-Driven-iOS-Development-Developers-Library/dp/0321774183

!

http://www.amazon.com/Growing-Object-Oriented-Software-Guided-Tests/dp/0321503627

!

http://martinfowler.com/articles/mocksArentStubs.html

Questions?

top related