ObjcUnit: How To Set Up a Project Builder Project

# How To Set Up A Project Builder Project For Unit-Testing With ObjcUnit As you might have noticed, there is no command-line test runner in the ObjcUnit distribution. Why? The answer is: because we could accomplish the same thing in a simpler way, using multiple targets in Project Builder, so that the project contains both tests and code, but produces an end-user target without any test code. Basically, what we do is to create a Project Builder project of the type we desire for our application or program Ð be it a "Foundation Tool" or a "Cocoa Document-based Application". Then we add an additional target for the tests. All classes in the main target that we want to test are added to this test target as well, so that the test cases can access them. When you build and run this target, all tests will be executed. ## Creating the Target Let's assume you just have created a new Project Builder project. This project contains one target with the same name as the project, as you can see in the Targets tab (hit Command-4). Create another target by clicking New Target... in the Project menu. Choose the Tool type at the end of the list of available tool types. Name your target "All Tests". Switch to the Files tab (hit Command-1) and open the Frameworks group and select Linked Frameworks. Choose Add Frameworks... in the Project menu and locate `ObjcUnit.framework` (see `Read Me.rtf` in the distribution for help on installing it). Add it to the All Tests target. Change the active target to All Tests in the Active Target popup button in the toolbar. Check the checkbox in front of Cocoa.framework in the Files tab, so that the test target links against that framework too. Now create a new file by clicking New File... in the File menu. Choose the Empty type and name the file `test_main.m`. Choose the All Tests target and type in the following and save the file: `// The test_main.m file #import <ObjcUnit/ObjcUnit.h> #import "AllTests.h" int main(int argc, const char *argv[]) { TestRunnerMain([AllTests class]); return 0; }` This is the `main()` function for your test target, which calls `TestRunnerMain()` with the class object for the `AllTests` class, of which the `+suite` method returns a `TestSuite` object that will contain all the tests in your project. (`TestRunnerMain()` returns either `NoFailuresOrErrorsOccurred`, `ErrorsOccurred` or `FailuresOccurred` in case you're interested.) ## Creating the Test Suite Now you need to create the `AllTests` class, so choose New File... again, but with the type Objective-C class instead. Name the file `AllTests.m` and make sure that the checkbox Also create "AllTests.h" is checked. Add these files to the All Tests target. The `AllTests` class can inherit any class, but is expected by `TestRunnerMain()` to implement the `+suite` class method, returning a `TestSuite` object with all tests in your project. Enter the following and save the files: `// The AllTests.h file #import <ObjcUnit/ObjcUnit.h> @interface AllTests : NSObject + (TestSuite *)suite; @end // The AllTests.m file #import "AllTests.h" @implementation AllTests + (TestSuite *)suite { TestSuite *suite = [TestSuite suiteWithName: @"My Tests"]; // Add your tests here ... return suite; } @end` By now your project has a second target for your tests, a `main()` function so that it can be run, and an `AllTests` class for grouping all your test cases in a test suite. This test suite is empty, though, but you can build and run your target (hit Command-R) and this will render the following output: `OK (0 tests) All Tests has exited with status 0.` ## Creating Tests Now you need to add some tests to your suite. Create a new Objective-C class in the All Tests target. Let it inherit `TestCase` (don't forget to import `ObjcUnit.h`) and add it to the test suite like this: `// In the AllTests class ... + (TestSuite *)suite { TestSuite *suite = [TestSuite suiteWithName: @"My Tests"]; // Add your tests here ... [suite addTest:[TestSuite suiteWithClass:[MyLittleTest class]]]; return suite; }` It's a really good habit to build and run the tests when adding a new test to the test suite. If you forget this, the test runner won't report failures or errors and you might think all is OK, when it isn't. Always start with a failing test. Building and running now will produce the following output (providing that you named your `TestCase` subclass "MyLittleTest"): `.F FAILURES!!! Tests run:1, Failures:1, Errors:0 There was 1 failure: 1) warning(_WarningTest)"No tests found in MyLittleTest" All Tests has exited with status 0.` The next thing you need to do is to start writing test cases. You write one test, build and see what errors you get, and eliminate them one by one - for complaints about undefined classes: create the class in the main target and add it to the test target as well, by checking the checkbox in the Files tab, them import them into your test class; for complaints about undefined methods: create the method with an empty body, returning some dummy value just to get it to compile. When it compiles, eliminate each test failure or error one by one. If a method doesn't return "Hello, world!" as it should, write the minimum amount of code needed to get the test to pass. Don't be afraid to hard code - get the test to pass and write another test that forces you to write more code. This bouncing back and forward between writing tests and writing code feels really good. Never write code unless your tests won't compile or if they fail when executing them. If you need help, please visit http://oops.se/cgi-bin/wiki?ObjcUnit and ask your questions there, or send us an e-mail at objcunit@oops.se. Have fun! Malte Tancred & Peter Lindberg Computer Programmers Oops AB, Sweden