📜 ⬆️ ⬇️

Cocoa Unit Tests

The following describes the basics of using OCUnit, a framework for creating unit tests integrated into Xcode. To visually try the described things, the code can be downloaded immediately. I wrote before the Xcode 4 era, so the pictures are a bit outdated.



So, for example, we have a certain category that extends the standard NSString class and adds a method for flipping its content:
')
//ExtendedString.h #import <Cocoa/Cocoa.h> @interface NSString (Extended) - (NSString *)invert; @end 

 //ExtendedString.m #import "ExtendedString.h" @implementation NSString (Extended) - (NSString *)invert { NSUInteger length = [self length]; NSMutableString *invertedString = [NSMutableString stringWithCapacity: length]; while (length > (NSUInteger)0) { unichar c = [self characterAtIndex: --length]; [invertedString appendString: [NSString stringWithCharacters: &c length: 1]]; } return invertedString; } @end 

Add to the project a new target. Let it be called "Tests":





Add a special unit test framework to the project - SenTestingKit:



It should be added only to the target Tests:



Create a group for sources called Tests and add new test files to it. By the way, in the folder with the project it is best to also create a physical subfolder Tests and there already place the source code with the tests:





Let's call it in an understandable way - ExtendedString_Test.m - and add it to the Tests target:



In addition, the Tests target will need to include the source code of the tested class itself, and not just the tests. As a result, the structure of the target in the project will look like this:



We give the test code, providing it with the necessary comments:

 //ExtendedString_Test.h #import <SenTestingKit/SenTestingKit.h> //   //       ,   , //        - : //@class MyClass; //  Mylass    ExtendedString_Test //,   NSString @interface ExtendedString_Test : SenTestCase // -    { NSString *_string; } @end 

 //ExtendedString_Test.m #import "ExtendedString_Test.h" #import "ExtendedString.h" //     @implementation ExtendedString_Test - (void)setUp //  ,        { _string = [[NSString alloc] initWithString: @"Hello world!"]; STAssertNotNil(_string, @"Construct error!"); // ,     } - (void)tearDown //  ,    { [_string release]; } - (void)testInvertString //    { STAssertEqualObjects(@"Hello world!", _string, @"String is not initialized!"); //  STAssertEqualObjects(@"!dlrow olleH", [_string invert], @"String is not inverted!"); //  } @end 

If everything is OK, then the assembly of the Tests Tests will simply be successful. If any of the asserts prove to be invalid, an error will be displayed during the build, as if it were a compilation error. Like that:



Now, how does this kitchen work?

During the build of the target with tests, a special bundle with the extension octest is created. The next step in building this target is to run a script with the following content:

$ {SYSTEM_DEVELOPER_DIR} / Tools / RunUnitTests
This thing in turn runs nested scripts, but it all comes down to calling / Developer / Tools / otest with certain parameters. The latter loads our test bundle into itself, finds SenTestCase's heir classes there and pulls the setUp method at the beginning, tearDown at the end, and between them it calls all the methods whose name begins with the word "test". Yes, Objective-C allows it :)

Therefore, all tests need to start with the prefix "test".

By the way, octest can be run manually. For example:

export OBJC_DISABLE_GC=YES # Garbage Collector
arch -i386 /Developer/Tools/otest ~/InvertString/build/Debug/Tests.octest # (i386),

The output will be something like this:

objc[22721]: GC: forcing GC OFF because OBJC_DISABLE_GC is set
objc[22721]: GC: forcing GC OFF because OBJC_DISABLE_GC is set
Test Suite '/Users/ium/InvertString/build/Debug/Tests.octest(Tests)' started at 2011-07-01 18:46:45 +0300
Test Suite 'ExtendedString_Test' started at 2011-07-01 18:46:45 +0300
/Users/ium/InvertString/Tests/ExtendedString_Test.m:21: error: -[ExtendedString_Test testInvertString] : 'Hello world' should be equal to 'Hello world!' String is not initialized!
2011-07-01 18:46:45.240 otest[22721:80f] !dlrow olleH
/Users/ium/InvertString/Tests/ExtendedString_Test.m:22: error: -[ExtendedString_Test testInvertString] : '!dlrow olle' should be equal to '!dlrow olleH' String is not inverted!
Test Case '-[ExtendedString_Test testInvertString]' failed (0.003 seconds).
Test Suite 'ExtendedString_Test' finished at 2011-07-01 18:46:45 +0300.
Executed 1 test, with 2 failures (0 unexpected) in 0.003 (0.003) seconds

Test Suite '/Users/ium/InvertString/build/Debug/Tests.octest(Tests)' finished at 2011-07-01 18:46:45 +0300.
Executed 1 test, with 2 failures (0 unexpected) in 0.003 (0.010) seconds

Well, this is for the version with errors, naturally. It all falls out in the log, by the way (Build> Build Results).

Having received this on sdterr during the build, Xcode immediately parses it, finds the “error:” keyword with the prefixed file name and line number, and nicely highlights the errors, like in the previous picture.

The set of macros for assertion checking is as follows:

 STFail(description, ...) //      ,  ,     STAssertNil(a1, description, ...) STAssertNotNil(a1, description, ...) STAssertTrue(expression, description, ...) STAssertFalse(expression, description, ...) STAssertEqualObjects(a1, a2, description, ...) //    ,    STAssertEquals(a1, a2, description, ...) //    STAssertEqualsWithAccuracy(left, right, accuracy, description, ...) STAssertThrows(expression, description, ...) STAssertThrowsSpecific(expression, specificException, description, ...) STAssertThrowsSpecificNamed(expr, specificException, aName, description, ...) STAssertNoThrow(expression, description, ...) STAssertNoThrowSpecific(expression, specificException, description, ...) STAssertNoThrowSpecificNamed(expr, specificException, aName, description, ...) STAssertTrueNoThrow(expression, description, ...) STAssertFalseNoThrow(expression, description, ...) 

The names speak for themselves, except for the commented. The ellipsis works as a substitution of parameters into the format string description. There, as usual, the percent sign.

To deepen your knowledge I recommend reading the official documentation and this .

Good luck!

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


All Articles