Quality Coding
Shares

What Would MacGyver Do? Quickly Hack a Spike Test!

Shares

I’ve been TDDing the “fetch characters” network request to the Marvel Comics API. But I’m anxious to see it work in a full round-trip to the actual Marvel server. Basically, I don’t want to go long without end-to-end feedback. How can I get the feedback I need to validate the current code?

Answer: Quickly hack together any way that works.

The agile workflow is to build, validate, and adjust course. Extreme Programming teaches us to use many feedback loops. And fast feedback is the primary benefit of TDD. …But TDD isn’t the only way I’ve gotten feedback on my Marvel Browser:

  • I did a spike solution to learn how to do request authentication for the Marvel Comics API.
  • Having TDD’d that authentication, I wrote an acceptance test to ensure that it works.

This time, I want to combine these two approaches. It’s a hybrid for which I don’t have a name. Maybe a “spike test”?

[This post is part of the series TDD Sample App: The Complete Collection …So Far]

The work so far

So far, I’ve TDD’d:

There are a couple of things remaining before I’m ready to initiate a request…

Start the data task

We’ve created a data task. But we need to start that task. Here’s a test to drive that requirement:

- (void)testFetchCharacters_ShouldStartDataTask
{
    QCOFetchCharactersRequestModel *requestModel = [self dummyRequestModel];
    NSURLSessionDataTask *mockDataTask = mock([NSURLSessionDataTask class]);
    [given([mockSession dataTaskWithURL:anything() completionHandler:anything()])
            willReturn:mockDataTask];

    [sut fetchCharactersWithRequestModel:requestModel];

    [verify(mockDataTask) resume];
}

You may feel funny seeing a mock return a mock. Normally, that’s something to avoid! Having to mock a mock to give you a mock is feedback that the design needs improvement.

But this case isn’t as bad as that. Here, we are using mockSession as a stub, not a mock. And all the stub does is return a (mock) data task.

We then call fetchCharactersWithRequestModel:, and confirm that doing so tells the data task to resume.

Time for a “spike test”!

Now the code has all the pieces we need to send a “fetch characters” request to Marvel. After refactoring, here’s the Service method:

- (void)fetchCharactersWithRequestModel:(QCOFetchCharactersRequestModel *)requestModel
{
    NSURL *url = [self URLForRequestModel:requestModel];
    NSURLSessionDataTask *dataTask = [self.session dataTaskWithURL:url completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
    }];
    [dataTask resume];
}

What it lacks is a way to get the response. I don’t know what the response handling should look like without TDDing the internal API. (One of the benefits of TDD is to see what an API will look like from the caller’s point of view.) Until I determine the API, I don’t want to write a full-blown acceptance test.

I want something like a spike solution. Quick and dirty, just enough to give me feedback. But this time, it’s not an experiment to determine a solution. It’s an experiment to see if what we have so far actually works. A spike test.

Artifacts are your friend

A spike test probably won’t use unit test assertions. We’re never going to automate it. It’s a one-shot manual test.

We want artifacts of some kind. NSLog will do fine for many spike tests. For something visual, use local variables with a breakpoint. That will let you use Xcode’s Quick Look to render something without having to do all the work.

In my case, here’s a hacked-together body of the currently empty completion handler:

NSLog(@"error: %@", error);
NSLog(@"response: %@", response);
NSString *str = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(@"data: %@", str);

Basically, I’m taking every argument to the completion handler and logging it.

Hack a trigger

We need a trigger, something to fire off the request. And we need to keep the Service around, so that it gets the response.

My main view controller is currently empty. Let’s have it do the work. It will initiate the request when the view loads:

@implementation ViewController

- (void)viewDidLoad
{
    [super viewDidLoad];
    QCOFetchCharactersMarvelService *service =
            [[QCOFetchCharactersMarvelService alloc] initWithSession:[NSURLSession sharedSession]
                                             authParametersGenerator:^NSString * {
                                                 return [QCOMarvelAuthentication URLParameters];
                                             }];
    QCOFetchCharactersRequestModel *requestModel =
            [[QCOFetchCharactersRequestModel alloc] initWithNamePrefix:@"Spider" pageSize:1 offset:0];
    [service fetchCharactersWithRequestModel:requestModel];
}

@end

Note that I need to provide the Service’s initializer with real arguments. I pass a real NSURLSession, and a block that calls the real QCOMarvelAuthentication. The request itself is for any character whose name starts with “Spider”. I figure the Marvel database will always return something for that! And I limit the page size to 1 result, to make things faster.

Now I fire up the app, and… I see results! I can even turn off my Wi-Fi and see error results.

(Because I use a separate app delegate for tests, this hack can remain in place for a while without affecting unit test runs.)

Spike tests are good for iteration demos

At the end of every iteration, the team should give a demo. Some teams struggle with this — they think features need to look like a product. So they skip the demo portion, and end up hunkering down for too long. The product owner is left wondering what progress has been made. (Or worse, is given a meaningless estimate like, “We’re 70% done.”)

Don’t skip the demo. Show the team’s progress, by any way that works.

I suppose some product owners might be put off by seeing NSLog results. But you can explain that a spike test shows actual results with the help of temporary code. You will gain greater trust by having regular demos. And the team will have a better sense of the actual progress.

When you’re done, delete the spike test

One way or another, when you’re done with the spike test, throw it away.

For a spike solution, I like to work in a branch to keep them from going into production code. The branch can be kept for reference, but it won’t mess up the main branch.

But for a spike test, it depends. You might keep it in a branch, and do the demo straight from your computer. But if you want to demo on someone else’s device, you might check your spike test straight into the master branch for your continuous delivery system. If you go this route, be sure to revert it later.

Summary

A “spike test” is a hybrid of a spike solution and an acceptance test. I doubt that anyone else calls it that, but I needed to call it something. The term may be new, but the idea is old.

Like a spike solution, a spike test is quick & dirty. The goal is fast feedback. Remember to delete it somehow.

A spike test lets you know if things are working so far, before the code is really done. Use it to validate your work in progress. You might also use it for an iteration demo — especially if you’re stuck wondering, “What do we show?” It may not be pretty, but you can show something!

Have you used spike tests for validation? How about for demos? Click here to leave a comment.

[This post is part of the series TDD Sample App: The Complete Collection …So Far]

About the Author Jon Reid

Jon is a coach and consultant on iOS Clean Code (Test Driven Development, unit testing, refactoring, design). He’s been practicing TDD since 2001. You can learn more about his background, or see what services he can bring to your organization.

follow me on:

Leave a Comment: