35

I am in the process of migrating my project from Xcode 4.6.3 to Xcode 5.0.2. The project's unit tests were developed with SenTestingKit/OCUnit. Now when I am running the tests in Xcode 5, I get an error from the RunUnitTests script telling me that

RunUnitTests is obsolete.

Possibly related is this note in the Xcode 5 release notes:

SenTestingKit and OCUnit are deprecated. Use the migrator to move to XCTest.

Unfortunately, I have not been able to find out more about this mysterious "migrator". Possibly my google-fu is lacking [again], so my main question is: How do I migrate unit tests from SenTestingKit/OCUnit to the new XCTest (with or without the "migrator")?

A secondary question, in case migrating is a complicated business: Is it possible to get Xcode 5 to run unit tests that are still based on SenTestingKit/OCUnit? After all these are merely deprecated, so they should still be around and functional.

herzbube
  • 12,320
  • 8
  • 42
  • 76
  • If you are crazy enough (like me) to want to run XCTest for iOS7 and OCUnit for iOS6, check out my [blog post](http://blog.7actionsdev.com/2014/04/using-ocunit-for-ios6-and-xctest-for.html). – phatmann Apr 04 '14 at 21:23

2 Answers2

59

Thanks to Shaggy Frog's answer we know that the mysterious "migrator" mentioned in the Xcode release notes is a wizard launched by selecting "Edit > Refactor > Convert To XCTest". I am going to write about my experience with this wizard in two parts. The first part is an incomplete answer to the primary question, the second part answers the secondary question.


Part 1: How to migrate from OCUnit to XCTest

The first thing you need to realize is that for the wizard to work, you need to select a unit test target. If you have the main target selected, the wizard simply does not list any targets to convert.

Once I found out about this, I was able to step through the wizard, but in my case the end result was still a spectacular failure! The wizard claimed that no source changes were necessary and that only build settings needed to be updated to migrate to XCTest. In the end, the wizard did not even manage to do that correctly: It did remove the reference to the SenTestingKit framework, but it did not put in a reference to the XCTest framework.

Anyway, what follows is a list of the changes that I had to manually make because the wizard failed to make them for me. If the wizard works better for you, you may not need to do all of these things.

  1. Remove the "Run Script" build phase from the unit test target
  2. Change the base class of all test case classes from SenTestCase to XCTestCase
  3. Change the imported header from <SenTestingKit/SenTestingKit.h> to <XCTest/XCTest.h>
  4. In test target's Build Settings, change Wrapper Extension from octest to xctest.
  5. Rename all assert macros from ST* to XCT* (e.g. STAssertTrue becomes XCTAssertTrue)
  6. Exception to the above: STAssertEquals needs to be renamed to XCTAssertEqual (note the missing "s" at the end). You will know that you have forgotten about this if you get this compiler warning: warning: implicit declaration of function 'XCTAssertEquals' is invalid in C99
  7. The new XCTest assert macros do not allow nil to be passed as the failure description. For instance, XCTAssertNotNil(anObject, nil) is not possible and must be changed to XCTAssertNotNil(anObject). You will know that you have this problem when you get this compiler error: error: called object type 'NSString *' is not a function or function pointer.
  8. If you do need to pass a failure description, the new XCTest assert macros require a constant expression for the format specifier, just as the NSString class method stringWithFormat: does. You will know that you have this problem when you get this compiler error: error: expected ')'. Some examples:
NSString* formatSpecifier = @"%@";
NSString* failureDescription = @"foo";
// These are OK
XCTAssertNotNil(anObject, @"foo")
XCTAssertNotNil(anObject, @"%@", failureDescription)
// These are not OK
XCTAssertNotNil(anObject, failureDescription);
XCTAssertNotNil(anObject, formatSpecifier, failureDescription);

Last but not least, as already mentioned further up, the reference to the XCTest framework needs to be added to the unit test target. You will know that you have forgotten this if you get linker errors such as Undefined symbols for architecture i386: "_OBJC_CLASS_$_XCTestCase", referenced from: foo.

Xcode 6 update: Linking against XCTest is no longer required in Xcode 6 (in fact XCTest is not even listed as an available framework anymore). Instead set the build setting CLANG_ENABLE_MODULES to YES (exposed in the UI as "Enable Modules (C and Objective-C)"). This will cause clang to automatically link against XCTest when it sees an #import <XCTest/XCTest.h> statement. Details are available in the "Modules" section of the clang documentation.


Part 2: How to run OCUnit tests in Xcode 5

At this point I got a linker error that made me realize that my mission to migrate to XCTest had failed. The reason: XCTest is not part of SDK 6.1, but I am still building my project with base SDK iOS 6.1 (this SO answer explains how to integrate SDK 6.1 into Xcode 5).

Since I am unable to continue with the migration, my solution for the moment is therefore to keep my unit tests based on SenTestingKit/OCUnit, until I find the time to upgrade my app to iOS 7. This is what I had to do in order to get the unit tests to run:

  1. Remove the "Run Script" build phase from the unit test target. This is all that is required to let Xcode execute unit tests via the "Test" action ( + U) while the unit test target is selected.
  2. This is not ideal, though, because I don't want to switch targets just to execute unit tests. Instead I want to execute unit tests while the main target is selected. The second step therefore is to modify the main target's Xcode scheme so that when I run the "Test" action, the unit test target's tests are executed instead.

The final solution is not as good as in Xcode 4.x where the unit tests were executed automatically every time that I ran the main target's "Run" or "Build" action. Unfortunately, it seems that I can't get this to work without a "Run Script" build phase.

Community
  • 1
  • 1
herzbube
  • 12,320
  • 8
  • 42
  • 76
  • 1
    In my project all seems converted to XCTest and works fine, but I still have OCUnit warning. After I perform automatic conversion again(get no code changes needed) - warning disappears, but it still there next time I open XCode. Any help with this? – Ilya K. Mar 24 '14 at 13:37
  • @IlyaK. You should ask a new question. This gives you more space to describe your problem. Also I am currently not working on any Xcode projects, so I won't be able to help you. – herzbube Mar 24 '14 at 17:30
  • It's a small question, I thought maybe somebody from this thread encountered this issue. But probably you'r right and I should open separate thread. – Ilya K. Mar 26 '14 at 12:15
  • I just added a step to change the Wrapper Extension in build settings to `xctest`. The wizard will usually will do it. – phatmann Apr 01 '14 at 03:21
  • There is also an occasional bug in Xcode (affecting at least 5.1) whereby even after migrating the tests, the "OCUnit is deprecated" warning remains, and attempting to use the migration tool fails to run (as it doesn't detect the test suite to migrate). I've found that when this happens, making the following change manually for the relevant target in the relevant project.pbxproj helps to silence the warning: Where it says the following: productType = "com.apple.product-type.bundle.ocunit-test"; Change to: productType = "com.apple.product-type.bundle.unit-test"; – mz2 Jun 21 '14 at 18:23
  • If you have tests that compare equality of NSRanges you should also change them from using XCTAssertEqual(range1, range2) to XCTAssertTrue(NSEqualRanges(range1, range2)). – sqwerl Jul 02 '14 at 01:33
  • Now that Xcode 6 is released, I request that this answer be updated to confirm everything is still applicable. Also requesting any information that will be applicable for people target Mac application. – William Entriken Oct 01 '14 at 14:13
  • @FullDecent My own project has been migrated to XCTest quite some time ago, so I don't have the motivation to do this research work for you. If you need the information for a project of yours, I suggest you simply try things out for yourself, and then post the results in another answer. – herzbube Oct 03 '14 at 20:34
  • @herzbube Thanks for your work so far, and sorry to sound like I was tasking you. Take 2. Hello XCode 6 users, can you confirm this is still good? – William Entriken Oct 04 '14 at 01:24
14

Edit -> Refactor -> Convert to XCTest

OCUnit tests will still work, but you might as well migrate. The changes end up being quite minimal.

Shaggy Frog
  • 27,118
  • 16
  • 85
  • 127
  • Aha, hidden under refactor... well, it could have been worse. Anyway, the second wizard screen should let me select the targets to convert. In my case the list is empty. Any ideas why? – herzbube Nov 16 '13 at 19:16
  • I am now sure that it must have something to do with the way how my unit test target is currently set up. I have tried adding a new empty unit test target (with File > New > Target), and that one shows up in the wizard perfectly fine. I will continue to investigate and post the result once I have the solution. – herzbube Nov 16 '13 at 19:37
  • 2
    xCode 6 it's now under the menu `Edit -> Convert -> To XCTest...` – ajmccall Apr 15 '15 at 21:12