Re: [Cedar] Questions from a Cedar newbie

335 views
Skip to first unread message

Adam Milligan

unread,
Jan 13, 2013, 10:57:19 AM1/13/13
to cedar-...@googlegroups.com
Hi James, 

For your first question, I don't have a definite answer; to a large extent which method you choose is a matter of personal preference.  Cedar supports both methods because Apple's OCUnit, built into Xcode, works one way, and other testing frameworks, like Rspec, which I used as inspiration work the other way.  When I initially wrote Cedar I wanted to be able to run specs easily from the command line and get clear output in the format I was used to (dots for passing examples, F's for failing examples, etc.).  I also wanted to depend on Xcode as little as possible.  And, finally, I did initially start with an attempt to integrate with the OCUnit runner, in the interest of reusing as much as possible, but I found the Test Bundle mechanism incredibly confusing and difficult to work with.  Later on, some other folks, primarily Dmitriy Kalinin, sat down and worked out how to integrate Cedar specs into the OCUnit Test Bundle mechanism, which is why we now support both.

Each has its ups and downs.  I would use the Test Bundle if you primarily use Xcode for running your specs, and you want all of the IDE integration, like shortcut keys.  This method has the advantage that you don't need to create a new target or executable for running your specs, so you don't have to change targets when you're running your application vs. the specs.  On the down side, you get a lot more output spew when you run from the command line, or on CI.  I personally also find it more difficult to set up and maintain, but you may have more luck than me.

Setting up a separate Spec Suite requires separate targets, and thus you have to remember to change the target when you run application vs. specs.  Some people consider this too high a price to pay for clean output.  I also like the fact that it allows you to create multiple spec targets; I nearly always create one target for UI-specific functionality (dependent on UIKit), and one target for non-UI-specific functionality.  I run the latter as an OSX command line executable, which means it runs significantly faster, and doesn't depend on running the simulator (which can be flaky and slow in comparison).  

There are two possible answers to your second question: one theoretical, and one practical.  The former is about the difference between various test double objects.  Probably the most referenced article on this, if not the best, is Martin Fowler's "Mock Aren't Stubs" (http://martinfowler.com/articles/mocksArentStubs.html).  I tried fairly hard when working out Cedar's test doubles to name them appropriately.

More practically, the main difference is between spy_on and fake_for.  The former method, spy_on, takes an existing object and instruments it so that you can later ask it what messages it has received.  A spec example that uses spy_on might look something like this:

it(@"should close the file", ^{
    spy_on(file);
    [someObject testedMethod];
    file should have_received("close");
});

In this case the object you're testing has a reference to a file object, and we want to make sure it interacts with that file object by closing it.  The file object is already there, so rather than replace it with a test double we just spy on the existing object.

In comparison, fake_for will make a completely new object out of thin air.  You can give it a class or a protocol, and it will create a double that responds to the methods defined by that class or protocol.  This is often useful for testing things like delegates:

it(@"should notify its delegate of button pushes", ^{
    id<CedarDouble, SomeControllerDelegate> delegate = nice_fake_for(@protocol(SomeControllerDelegate));
    SomeController *controller = [[SomeController alloc] initWithDelegate:delegate];

    [controller.someButton sendActionsForControlEvents:UIControlEventTouchUpInside];

    delegate should have_received("didTapSomeButton");
});

As I'm sure you noticed, I used the nice_fake_for here; fake_for and nice_fake_for will both create a double object that responds to methods on the specified class or protocol, but the object created by fake_for will respond by throwing an exception.  If you use fake_for you need to explicitly allow the subject you're testing to call certain methods by using stub_method.  This can be useful if you want to make certain that your code doesn't call other methods, or if calling a double method doesn't make sense unless it returns a specific value (by default it will always return just nil or 0).  For instance:

it(@"should ask the delegate for permission to do stuff", ^{
    id<CedarDouble, SomeControllerDelegate> delegate = fake_for(@protocol(SomeControllerDelegate));
    SomeController *controller = [[SomeController alloc] initWithDelegate:delegate];

    delegate stub_method("shouldDoStuff").and_return(YES);

    [controller.someButton sendActionsForControlEvents:UIControlEventTouchUpInside];
    controller should have_done_stuff; // <- not a real matcher
});

I hope that helps.

On Sat, Jan 12, 2013 at 5:09 PM, frosty <james...@gmail.com> wrote:
Hi all,

I'm currently investigating my options for doing TDD / BDD with iOS, and Cedar looks to be one of the best (whilst Kiwi seems to also have a big following, I found Cedar much easier to get started with, and I love the command line and app-based spec runners). However, I have a couple of questions that I can't find covered anywhere in the wiki pages, and wondered if someone here could help:

- I'd like to see on the wiki more of a discussion off the pros / cons of choosing either a Test Bundle and Spec Suite. I understand that they run differently, but I don't really 'get' why I'd choose one over the other. What's the most common choice?
- This might be a general testing question rather than Cedar specific (I've only really done a small amount of testing before with Ruby), but what's the difference between spy_on, fake_for and nice_fake_for? Again, when would I use each of these?

I hope someone can help explain these few issues!

Thanks
James

Reply all
Reply to author
Forward
0 new messages