Clean This Code

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"
 5 
 6 CONTEXT(OCDSpecExpectation)
 7 {
 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];
13 
14                     OCDSpecExpectation *expectation = [[[OCDSpecExpectation alloc] initWithObject:actualObject inFile:@"" atLineNumber:0] autorelease];
15                     
16                     [expectation toBeEqualTo:expectedObject];
17                     
18                     [expect(actualObject) toBeEqualTo:expectedObject];
19 
20               }),
21              
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];
26                     
27                     OCDSpecExpectation *expectation = [[[OCDSpecExpectation alloc] initWithObject:actualObject inFile:@"" atLineNumber:0] autorelease];
28                     
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];
37                         
38                         [expect([exception reason]) toBeEqualTo:expectedReason];
39                     }
40                 }),
41              
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];
46                     
47                     OCDSpecExpectation *expectation = [[[OCDSpecExpectation alloc] initWithObject:actualObject inFile:@"FILENAME" atLineNumber:120] autorelease];
48                     
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"];
57                         
58                         if (![[[exception userInfo] objectForKey:@"line"] isEqual:[NSNumber numberWithLong:120]])
59                             FAIL(@"Should have had line number 120, didn't");
60                     }
61                     
62                 }),
63              
64              it(@"Is created helpfully by the expect macro",
65                 ^{
66                     NSObject *innerObject;
67                     
68                     OCDSpecExpectation *expectation = expect(innerObject);
69                     
70                     if (expectation.line != __LINE__ -2)
71                         FAIL(@"Line Number is wrong");
72                     
73                     [expect(expectation.file) toBeEqualTo:[NSString stringWithUTF8String:__FILE__]];
74                 }),
75            nil);
76 }
Eric Smith, Software Craftsman

Eric Smith has a Master's Degree in Video Game Development from DePaul University.