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!

#import "OCDSpec/OCDSpec.h"
#import "OCDSpec/OCDSpecExpectation.h"
#import "OCDSpec/OCDSpecFail.h"
#import "Specs/Mocks/MockObjectWithEquals.h"

CONTEXT(OCDSpecExpectation)
{
				describe(@"The Expecation",
				^{
								MockObjectWithEquals *actualObject = [[[MockObjectWithEquals alloc] init] autorelease];
								MockObjectWithEquals *expectedObject = [[[MockObjectWithEquals alloc] init] autorelease];

								OCDSpecExpectation *expectation = [[[OCDSpecExpectation alloc] initWithObject:actualObject inFile:@"" atLineNumber:0] autorelease];
								
								[expectation toBeEqualTo:expectedObject];
								
								[expect(actualObject) toBeEqualTo:expectedObject];
				}),
				
				it(@"throws a failure when the objects aren't equal with an explanatory reason if the two objects are not equal to each other",
				^{
								MockObjectWithEquals *actualObject = [[[MockObjectWithEquals alloc] initAsNotEqual] autorelease];
								MockObjectWithEquals *expectedObject = [[[MockObjectWithEquals alloc] init] autorelease];
								
								OCDSpecExpectation *expectation = [[[OCDSpecExpectation alloc] initWithObject:actualObject inFile:@"" atLineNumber:0] autorelease];
								
								@try
								{
												[expectation toBeEqualTo:expectedObject];
												FAIL(@"Code did not throw a failure exception");
								}
								@catch (NSException *exception)
								{
												NSString *expectedReason = [NSString stringWithFormat:@"%@ was expected to be equal to %@, and isn't", actualObject, expectedObject];
												[expect([exception reason]) toBeEqualTo:expectedReason];
								}
				}),
				
				it(@"throws a failure with the line and file passed in - i.e. uses OCDSpecFail",
				^{
								MockObjectWithEquals *actualObject = [[[MockObjectWithEquals alloc] initAsNotEqual] autorelease];
								MockObjectWithEquals *expectedObject = [[[MockObjectWithEquals alloc] init] autorelease];
								
								OCDSpecExpectation *expectation = [[[OCDSpecExpectation alloc] initWithObject:actualObject inFile:@"FILENAME" atLineNumber:120] autorelease];
								
								@try
								{
												[expectation toBeEqualTo:expectedObject];
												FAIL(@"Code did not throw a failure exception");
								}
								@catch (NSException *exception)
								{
												[expect([[exception userInfo] objectForKey:@"file"]) toBeEqualTo:@"FILENAME"];
												if (![[[exception userInfo] objectForKey:@"line"] isEqual:[NSNumber numberWithLong:120]])
												{
																FAIL(@"Should have had line number 120, didn't");
												}
								}
				}),
				
				it(@"Is created helpfully by the expect macro",
				^{
								NSObject *innerObject;
								
								OCDSpecExpectation *expectation = expect(innerObject);
								
								if (expectation.line != __LINE__ -2)
												FAIL(@"Line Number is wrong");
								
								[expect(expectation.file) toBeEqualTo:[NSString stringWithUTF8String:__FILE__]];
				}),
				nil);
}