Clean This Code

Clean This Code

Eric Smith
Eric Smith

April 23, 2011

If you follow me on twitter @paytonrules you’ve probably seen me griping at various times about writing a testing framework for Objective-C.

I’m currently writing my first expectations, and bootstrapping a testing framework continues to be an interesting problem, which I’ll probably write more about later.

I find I continually have to go backwards on the tests to clean them, because as I implement more features I start using them in earlier tests. Meanwhile I want to clean the test I’ve recently written, often without the benefits of features I don’t have yet.

The code below are the tests for my first expectations. Currently I see a lot of duplication, and I’m curious how you might go about cleaning it with the framework in it’s current state. Keep in mind these rules:

  • I don’t have a before or after feature yet, and since some developers swear that using before and after is a bad practice, you can’t use that.

  • I don’t have expectations that take primitive types, which is why you see expect(obj) in some places, but a crude check then FAIL in others. I will, but don’t yet.

  • By using this DSL you don’t have access to a class to put variables on like you would in an XUnit style test. I have no idea of OCMock will work for mocking objects in my framework yet.

Given all those constraints—clean this code!

 1#import "OCDSpec/OCDSpec.h"
 2#import "OCDSpec/OCDSpecExpectation.h"
 3#import "OCDSpec/OCDSpecFail.h"
 4#import "Specs/Mocks/MockObjectWithEquals.h"
 8 describe(@"The Expecation",
 9 it(@"delegates beEqualTo to equalTo on the object its holding", 
10 ^{
11 MockObjectWithEquals *actualObject = [[[MockObjectWithEquals alloc] init] autorelease];
12 MockObjectWithEquals *expectedObject = [[[MockObjectWithEquals alloc] init] autorelease];
14 OCDSpecExpectation *expectation = [[[OCDSpecExpectation alloc] initWithObject:actualObject inFile:@"" atLineNumber:0] autorelease];
16 [expectation toBeEqualTo:expectedObject];
18 [expect(actualObject) toBeEqualTo:expectedObject];
20 }),
22 it(@"throws a failure when the objects aren't equal with an explanatory reason if the two objects are not equal to each other",
23 ^{
24 MockObjectWithEquals *actualObject = [[[MockObjectWithEquals alloc] initAsNotEqual] autorelease];
25 MockObjectWithEquals *expectedObject = [[[MockObjectWithEquals alloc] init] autorelease];
27 OCDSpecExpectation *expectation = [[[OCDSpecExpectation alloc] initWithObject:actualObject inFile:@"" atLineNumber:0] autorelease];
29 @try
30 {
31 [expectation toBeEqualTo:expectedObject];
32 FAIL(@"Code did not throw a failure exception");
33 }
34 @catch (NSException *exception)
35 {
36 NSString *expectedReason = [NSString stringWithFormat:@"%@ was expected to be equal to %@, and isn't", actualObject, expectedObject];
38 [expect([exception reason]) toBeEqualTo:expectedReason];
39 }
40 }),
42 it(@"throws a failure with the line and file passed in - i.e. uses OCDSpecFail", 
43 ^{
44 MockObjectWithEquals *actualObject = [[[MockObjectWithEquals alloc] initAsNotEqual] autorelease];
45 MockObjectWithEquals *expectedObject = [[[MockObjectWithEquals alloc] init] autorelease];
47 OCDSpecExpectation *expectation = [[[OCDSpecExpectation alloc] initWithObject:actualObject inFile:@"FILENAME" atLineNumber:120] autorelease];
49 @try
50 {
51 [expectation toBeEqualTo:expectedObject];
52 FAIL(@"Code did not throw a failure exception");
53 }
54 @catch (NSException *exception)
55 {
56 [expect([[exception userInfo] objectForKey:@"file"]) toBeEqualTo:@"FILENAME"];
58 if (![[[exception userInfo] objectForKey:@"line"] isEqual:[NSNumber numberWithLong:120]])
59 FAIL(@"Should have had line number 120, didn't");
60 }
62 }),
64 it(@"Is created helpfully by the expect macro",
65 ^{
66 NSObject *innerObject;
68 OCDSpecExpectation *expectation = expect(innerObject);
70 if (expectation.line != __LINE__ -2)
71 FAIL(@"Line Number is wrong");
73 [expect(expectation.file) toBeEqualTo:[NSString stringWithUTF8String:__FILE__]];
74 }),
75 nil);