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.
13. 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
17. What is Unit Test?
Unit tests are small pieces of code that
test the behavior of other code.
18. 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.
27. A good unit test:
is Trustworthy (when you see its result, you
don’t need to debug the code just to be sure)
28. 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
34. 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
35. 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
36. 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
37. 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
40. XCTest
• Provided by Xcode
• New iOS application projects automatically include a unit
testing target
41. XCTest
• Provided by Xcode
• New iOS application projects automatically include a unit
testing target
• Test classes do not have a header file
42. 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
43. 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
44. 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
45. 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__);
}
46. 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");
}
52. 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.
53. Constructor Injection
the dependencies are provided through a
class constructor
- (id)initWithNotificationCenter:(NSNotificationCenter *)notificationCenter
54. Setter Injection
the client exposes a setter method that the
injector uses to inject the dependency
@property (nonatomic, weak) NSUserDefaults* userDefaults;
59. is a fake object in the system that verifies the
object under test interacted as expected with
the fake object.
Mock Object
60. 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
61. 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
65. What we need?
•an object that responds to
addObserver:selector:name:object:
•possibility to record a call
•check for notification’s name
•verification
66. @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
68. - (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?
71. 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
72. 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];
73. 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]];
77. How to start with Existing Project
• All developers in team must write and run tests
78. How to start with Existing Project
• All developers in team must write and run tests
• Start with warnings and bugs
79. How to start with Existing Project
• All developers in team must write and run tests
• Start with warnings and bugs
• Isolate modules and classes
80. 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
81. 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