📜 ⬆️ ⬇️

Let unit-testing begin (Objective-C)

This article focuses on testing for Objective-C using Xcode 6. It discusses the standard testing library and the third-party OCMock library. Experienced developers may not find too useful information here, as well as those who have recently taken this path - the article will reveal the necessary basic knowledge of writing unit tests in Objective-C language.

For basic testing, please contact here .
For the basics of unit testing here .

And now we will begin the study of unit testing in Objective-C.

Step 1. Basics


Create a new iOS project.
')
Screenshots




Xcode, as we can see, he himself created for us a directory for tests DemoUnitTestingTests and the file DemoUnitTestingTests.m. What we see here:

Standard library for testing:

#import <XCTest/XCTest.h> 

The fact that our class DemoUnitTestingTests is inherited from the class XCTestCase (we will not go into details).

The method called before running each test:

 - (void)setUp { [super setUp]; // Put setup code here. This method is called before the invocation of each test method in the class. } 

Method called after the result of each test:

 - (void)tearDown { // Put teardown code here. This method is called after the invocation of each test method in the class. [super tearDown]; } 

And directly 2 test. The first is a demo test, the second is to demonstrate the performance test:

 - (void)testExample { // This is an example of a functional test case. XCTAssert(YES, @"Pass"); } - (void)testPerformanceExample { // This is an example of a performance test case. [self measureBlock:^{ // Put the code you want to measure the time of here. }]; } 

As you can see, all tests begin with the word test and are automatically included in the test list.

Step 2. Information


Some rules and comments:
All tests run independently.
Do maximum class isolation
The name of the test should reflect its purpose.
The result of the test should not affect the main code

Please do not think that writing tests will save your code from bugs once and for all. Your test may not have been written correctly.

Step 3. Getting to the test


We select the following functions for testing from the standard library for testing XCTest, as well as write tests using them.

Always a mistake
 - (void)testAlwaysFailed { /* 1 -   */ XCTFail(@"always failed"); } 


Equality of basic types
 - (void)testIsEqualPrimitive { /* 1 - 1 2 - 2  3 -    -       */ int primitive1 = 5; int primitive2 = 5; XCTAssertEqual(primitive1, primitive2, @"(%d) equal to (%d)", primitive1, primitive2); } 


Basic inequality
 - (void)testIsNotEqualPrimitive { /* 1 - 1 2 - 2  3 -    -       */ int primitive1 = 5; int primitive2 = 6; XCTAssertNotEqual(primitive1, primitive2, @"(%d) not equal to (%d)", primitive1, primitive2); } 


Equality with an error of basic types
 - (void)testIsEqualWithAccuracyPrimitive { /* 1 - 1 2 - 2 3 -     4 -    -       */ float primitive1 = 5.012f; float primitive2 = 5.014f; float accuracy = 0.005; XCTAssertEqualWithAccuracy(primitive1, primitive2, accuracy, @"(%f) equal to (%f) with accuracy %f", primitive1, primitive2, accuracy); } 


Inequality with the error of the basic types
 - (void)testIsNotEqualWithAccuracyPrimitive { /* 1 - 1 2 - 2 3 -     4 -    -       */ float primitive1 = 5.012f; float primitive2 = 5.014f; float accuracy = 0.001; XCTAssertNotEqualWithAccuracy(primitive1, primitive2, accuracy, @"(%f) not equal to (%f) with accuracy %f", primitive1, primitive2, accuracy); } 


Check on BOOL YES
 - (void)testIsTrue { /* 1 - boolean    2 -    -      */ BOOL isTrue = YES; XCTAssertTrue(isTrue); } 


Check for BOOL NO
 - (void)testIsFalse { /* 1 - boolean    2 -    -      */ BOOL isTrue = NO; XCTAssertFalse(isTrue); } 


Check for nil
 - (void)testIsNil { /* 1 -   2 -    -      */ id foo = nil; XCTAssertNil(foo, @"pointer:%p", foo); } 


Check for NOT nil
 - (void)testIsNotNil { /* 1 -   2 -    -      */ id foo = @""; XCTAssertNotNil(foo); } 


Comparing primitives by more (>)
 - (void)testGreaterPrivitive { /* 1 - 1 2 - 2  3 -    -      */ int privitive1 = 4; int privitive2 = 3; XCTAssertGreaterThan(privitive1, privitive2); } 


Comparing primitives for greater than or equal to (> =)
 - (void)testGreaterOrEqualPrivitive { /* 1 - 1 2 - 2  3 -    -      */ int privitive1 = 4; int privitive2 = 4; XCTAssertGreaterThanOrEqual(privitive1, privitive2); } 


Comparing primitives for less (<)
 - (void)testLessPrivitive { /* 1 - 1 2 - 2  3 -    -      */ int privitive1 = 3; int privitive2 = 4; XCTAssertLessThan(privitive1, privitive2); } 


Comparing primitives for less or equal (<=)
 - (void)testLessOrEqualPrivitive { /* 1 - 1 2 - 2  3 -    -      */ int privitive1 = 4; int privitive2 = 4; XCTAssertLessThanOrEqual(privitive1, privitive2); } 


Check for throwing an exception
 - (void)testThrowException { /* 1 - //  2 -    -      */ void (^block)() = ^{ @throw [NSException exceptionWithName:NSGenericException reason:@"test throw" userInfo:nil]; }; XCTAssertThrows(block()); } 


Check for NOT throwing an exception
 - (void)testNoThrowException { /* 1 - //  2 -    -      */ void (^block)() = ^{ }; XCTAssertNoThrow(block()); } 


Check for throwing an exception by the exception class
Class MyException derived from NSException
 @interface MyException : NSException @end @implementation MyException @end 

 - (void)testThrowExceptionClass { /* 1 - // 2 -    3 -    -      */ void (^block)() = ^{ @throw [MyException exceptionWithName:NSGenericException reason:@"test throw" userInfo:nil]; }; XCTAssertThrowsSpecific(block(), MyException); } 


Check for throwing an exception EXCELLENT from the exception class
Class MyException derived from NSException
 @interface MyException : NSException @end @implementation MyException @end 

 - (void)testNoThrowExceptionClass { /* 1 - // 2 -    3 -    -      */ void (^block)() = ^{ @throw [NSException exceptionWithName:NSGenericException reason:@"test throw" userInfo:nil]; }; XCTAssertNoThrowSpecific(block(), MyException); } 


Check for throwing an exception by the exception class with the name
Class MyException derived from NSException
 @interface MyException : NSException @end @implementation MyException @end 

 - (void)testThrowWithNamedExceptionClass { /* 1 - // 2 -   3 -    4 -    -      */ NSString *nameException = @"name expection"; void (^block)() = ^{ @throw [MyException exceptionWithName:nameException reason:@"test throw" userInfo:nil]; }; XCTAssertThrowsSpecificNamed(block(), MyException, nameException); } 


Check for throwing an exception EXCELLENT from the exception class named
Class MyException derived from NSException
 @interface MyException : NSException @end @implementation MyException @end 

 - (void)testNoThrowWithNamedExceptionClass { /* 1 - // 2 -   3 -    4 -    -      */ NSString *nameException = @"name expection"; void (^block)() = ^{ @throw [MyException exceptionWithName:[nameException stringByAppendingString:@"123"] reason:@"test throw" userInfo:nil]; }; XCTAssertNoThrowSpecificNamed(block(), MyException, nameException); } 


Equality of objects
 - (void)testEqualObject { /* 1 -   isEqual 2 -   isEqual  3 -    -      */ id obj1 = @[]; id obj2 = @[]; XCTAssertEqualObjects(obj1, obj2, @"obj1(%@) not equal to obj2(%@))", obj1, obj2); } 


Object Inequality
 - (void)testNoEqualObject { /* 1 -   isEqual 2 -   isEqual  3 -    -      */ id obj1 = @"name"; id obj2 = @{}; XCTAssertNotEqualObjects(obj1, obj2, @"obj1(%@) not equal to obj2(%@))", obj1, obj2); } 


Time delay check
 - (void)testAsync { /* 1.     (     ) 2.    3.   4.  fulfill     XCTestExpectation    fulfill     -     */ XCTestExpectation *expectation = [self expectationWithDescription:@"block not call"]; NSTimeInterval timeout = 1.0f; [expectation performSelector:@selector(fulfill) withObject:nil afterDelay:0.3f]; [self waitForExpectationsWithTimeout:timeout handler:nil]; } 


Step 4. Testing code with dependencies


Come to the most interesting part of the article. If you carefully read the first 2 links, then you already know that for unit testing you need to isolate the class from dependencies. You can write everything by hand, but we take ready. Take the OCMock library.

Install using CocoaPods :
pod 'OCMock', '~> 3.1.2'

Consider the most frequent problems when writing tests, when there are dependencies, and see how to cope with them using the OCMock library. Let's create for demonstration the classes ClassA and ClassB.

Dependence on another class
 - (void)testInit { /*  mock   ClassB     init  ClassA ,  classA.classB     ,   mockClassB */ id mockClassB = OCMClassMock([ClassB class]); ClassA *classA = [[ClassA alloc] initWithClassB:mockClassB]; XCTAssertEqual(classA.classB, mockClassB); } 

Data from another object
 - (void)testStub { /*  mock   ClassB   stub ,   .      ClassB    */ NSString *expectedInfo = @"info"; id mockClassB = OCMClassMock([ClassB class]); OCMStub([mockClassB info]).andReturn(expectedInfo); NSString *info = [mockClassB info]; XCTAssertEqualObjects(info, expectedInfo); } 

Data from another object depending on input data
 - (void)testStubWithArg { /*  mock   ClassB   stub ,        */ id mockClassB = OCMClassMock([ClassB class]); NSInteger expectedFactorial3 = 6; NSInteger expectedFactorial5 = 120; OCMExpect([mockClassB factorial:3]).andReturn(expectedFactorial3); OCMExpect([mockClassB factorial:5]).andReturn(expectedFactorial5); NSInteger factorial3 = [mockClassB factorial:3]; NSInteger factorial5 = [mockClassB factorial:5]; XCTAssertEqual(factorial3, expectedFactorial3); XCTAssertEqual(factorial5, expectedFactorial5); } 

Notification from another object
 - (void)testNotification { /*  mock   ClassB   stub ,    mock observer        mock         mock observer */ id mockClassB = OCMClassMock([ClassB class]); NSString *notificationName = @"notification name"; NSNotification *notification = [NSNotification notificationWithName:notificationName object:mockClassB]; OCMStub([mockClassB postNotification]).andPost(notification); id mockObserver = OCMObserverMock(); [[NSNotificationCenter defaultCenter] addMockObserver:mockObserver name:notificationName object:mockClassB]; OCMExpect([mockObserver notificationWithName:notificationName object:mockClassB]); [mockClassB postNotification]; OCMVerifyAll(mockObserver); } 


Calling another object's method
It does not differ in logic from the example above. We write expectations and check if they were caused.
 - (void)testVerifyExpect { /*  mock   ClassB      info  postNotification        mock  */ id mockClassB = OCMClassMock([ClassB class]); OCMExpect([mockClassB info]); OCMExpect([mockClassB postNotification]); [mockClassB info]; [mockClassB postNotification]; OCMVerifyAll(mockClassB); } 


Dependencies on the current state of the object
 - (void)testPartialMockObject { /*    ClassA,      count.  count  readonly */ id classA = [[ClassA alloc] initWithCount:3]; XCTAssertEqual([classA count], 3); id partialMock = OCMPartialMock(classA); OCMStub([partialMock count]).andReturn(41); XCTAssertEqual([classA count], 41); } 


Call at block object with delay
 - (void)testStubWithBlock { /*  mock   ClassB   stub ,       mock   ClassA      ,          */ void (^block)() = [OCMArg checkWithBlock:^BOOL(id value) { return YES; }]; id mockClassB = OCMClassMock([ClassB class]); OCMExpect([mockClassB setBlock:block]); id mockClassA = OCMClassMock([ClassA class]); OCMStub([mockClassA useBlockInClassB]).andDo(^(NSInvocation *invocation) { dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ [mockClassB setBlock:^{ }]; }); }); [mockClassA useBlockInClassB]; OCMVerifyAllWithDelay(mockClassB, 2); } 


For most tasks this should be enough. I propose to read more here .

Step 5. Configuring the launch of logic tests


When running tests (for example, using the cmd + u combination), the main code is first run and only after that the tests. This is bad. Often there are situations when the program crashes when the main code is executed and the tests do not run. Fix it.

Let's create another AppDelegate (AppDelegateForTest), but to run the tests (when creating, choose to add to the main Target).

Change main.c
 int main(int argc, char *argv[]) { @autoreleasepool { Class appDelegateClass = (NSClassFromString(@"XCTestCase") ? [TestingSAAppDelegate class] : [SAAppDelegate class]); return UIApplicationMain(argc, argv, nil, NSStringFromClass(appDelegateClass)); } } 


Is done. Now our tests run independently of our main code.

I think this article can be finished. Were considered functions for testing from the standard library, as well as the most necessary of the library OCMock. The project source can be downloaded here .

Good luck with your testing!

Source: https://habr.com/ru/post/258953/


All Articles