This post is part of an ongoing series about Unit Testing.
we wrote learning tests to figure out how to work with Ruby’s built-in
OptionParser class to handle command-line arguments.
Now it’s time to start implementing our application.
We’re building a simple command-line application that can do two things:
Convert an amount of money from one currency to another.
Show a list of all currencies supported by the application.
We may add a web front-end to this later or completely replace the command-line interface with a web interface.
Testing the CLI
Earlier, we wrote some acceptance tests that form the outermost layer of our test suite. Now we’re going to peel back the onion and work our way into the core of the application. We’re working from the outside of the application in towards the center. This is known as an outside-in approach.
The next layer of the application is the command-line interface (CLI). Because we want our tests to run as fast as possible, we’d prefer not to test the CLI by running it in a separate process and capturing the output; our Cucumber step definitions will likely do that. Instead, we want to run the tests in the same process as the CLI.
The way to do that is to make the executable a very thin wrapper over
an object that we can test directly. In a fit of originality, we’ll
call that object
We’ll use RSpec this time.
Simple First Test
Let’s start with a simple test just to get everything hooked up. We’ll start by checking that calling our CLI with no arguments results in a usage message being printed.
This is a pretty basic test, but the main thing to note is that we’re
testing only externally-visible behavior. We’re not testing anything
internal. Most notably, we’re not directly testing the
that this code uses internally. We learned how to use that class when
our learning tests,
but now it’s just an internal implementation detail that we want to
hide away. The parts of it that we care about will be tested through
the public interface of the
CLI object. If we later want to use
something else to handle the command-line, we can swap out our
existing implementation with no changes to the specs.
We’re also not testing the exact format of the output. As you may recall from the learning tests, the usage output was one of the most brittle parts of the option parser, so we don’t want to couple our tests to that too heavily.
There are a few other minor things to note:
I’ve embedded the outer
describeblock in the same module that contains the code I’m testing; that way, I can refer to any classes in that module without having to prefix them with the module name.
I’ve introduced a
letfor the arguments we’re going to pass to the
CLIobject. I talked about this in an earlier post. I like to have the
letdefinitions in each context show exactly what’s special about the context.
We’re using RSpec’s
outputmatcher to capture and test the output.
Let’s move on to a more involved test now. What happens when our CLI is asked to convert an amount from one currency to another?
What should that look like on the command-line? This is the main use
case of our system, so let’s make that as easy as possible and just
take three positional arguments: the amount, the source currency, and
the target currency. A typical invocation might look like
100 USD EUR.
Let’s start with what we’d like to see:
Notice that I’ve now extracted a helper method,
run_cli, to actually
run the application. This removes noise and duplication from the
tests, allowing me to focus more on the important parts.
In its current state, this test obviously won’t pass. Where does this
91.87 come from? This is where we need to allow the specs to
drive our design.
Following the approach that Sandi Metz advocates in her book, let’s think about the messages we need.
We really only need a single message, maybe named
takes the amount and the two currencies and returns the converted
Now that we’ve identified the message, we need to think about what
object we should send the message to. We don’t have any other objects
CLI so we probably need a new one. Let’s call it an
We don’t really know how
Exchange will work; we just know
what message we want to send it and what kind of response we need
back. That’s enough information to finish our current test if we use
a test double.
We need to identify what kind of message we’re talking about. If we follow the command-query separation approach, there are only two choices:
Queries return a result, but do not change the observable state of the system (no side effects).
Commands change the state of the system, but do not return a value.
In our case, the
convert method returns a value and has no
observable side-effects, making it a query message. According to
Sandi Metz’ approach, we don’t need to test outgoing query messages;
however, we may need to
stub them in
order to test the behavior of our object.
That’s certainly true in our case; we need to stub out the
method in order to get back our magic
91.87 value. Let’s see what
that looks like:
In RSpec, stubs are defined using
allow. I’ve chosen to be explicit
about the arguments I’m expecting to be sent to the stub as well.
Now we have to define what
exchange is and how the
CLI gets to
know about it. That happens up at the top of our spec; we’ll use a
to tell the
CLI about the
In order to define a test double in RSpec, you can use the
method or one of its siblings. In this case, I’m using the
instance_double method and passing in the
instance_double creates a
which only allows us to mock and/or stub methods that are implemented
by the class we’re doubling. This helps us avoid the problem of our
real interface getting out of sync with our tests. I use verifying
doubles whenever I can.
We’re injecting the
exchange using a keyword parameter. In order to
avoid extra pain for our clients, we can make it an optional keyword
with the default value being an instance of the real exchange class.
In order to get past the verifying double, we also have to add a
skeleton version of the
For now, we don’t need to think about the implementation of this class
at all; I’ll often use a
fail that will remind me to come
back and implement this method. Even better is to use some kind of
marker method to make
it easy to search for any work we still have left to do.
For now, we can stay focused on the
CLI class. Once it’s done, we
can start fleshing out
Exchange using TDD.
We can use the same approach to test-drive the currency list feature
of the CLI. Once again, we need to decide what the command-line
interface should look like. Let’s just provide a simple flag option;
when it is present, we’ll print out a list:
We also need to decide what messages we need to send for this feature.
We need to ask some object for the list of supported currencies, maybe
by sending a
currency_list message. We’ll expect back some kind of
currency list; it might be an
Array or other
Enumerable, or it
might be a custom
CurrencyList object. We can decide that later.
Once again, we look at the objects we have:
Should either of these handle the
currency_list method? I think it
makes sense for the
Exchange object to handle it; it needs to know
about currencies in order to convert them, so it would make sense for
it to know about what currencies are supported.
Once we get back the currency list, we need to print it out. That
seems like it might be a
CLI responsibility. It certainly doesn’t
belong to the
Exchange; that class shouldn’t be responsible for the
format of the output.
As we start to think about the
CLI tests we’ll need to write for the
printing, though, they seem kind of involved. And testing the
different cases by changing the stubbed return value of the
currency_list method seems tedious. It seems like maybe printing is
a separate responsibility that belongs somewhere else.
Let’s implement a message named
format that takes a currency list
and returns a string containing the formatted output. None of our
existing objects seem to be the right place for this behavior, so
let’s introduce a new class named
Once again, we can use a verifying test double, dependency injection, and a skeleton implementation to write the spec.
Both messages are query messages again, so we just need to stub them out.
We’re using a non-verifying
double for the currency list; we don’t
care what it actually is here, as long as the same object that comes
back from the
currency_list method gets passed along to the
formatter. By using a simple
double, we can decouple this test from
that decision, because it really doesn’t matter here. We don’t want
this test to change or break when we later decide what a currency list
There are more specs we’d want to write for the
CLI: what happens
when errors occur? Do we exit with correct error codes? What happens
if both the
--list and the conversion parameters are supplied? I’ll
leave those as an exercise for now, but we may talk about them again
Once we’ve finished testing the
CLI, we can go ahead and delete the
learning tests we wrote for the
OptionParser. We learned what we
needed to learn and captured the important parts in our
We also have to test-drive the implementation of the
ListFormatter classes. We’ll talk about the
Exchange in the next
ListFormatter is a simple object with a single
method that will probably be a pure function; we’ve
already talked about testing those,
so I won’t explore that further here.
I hope this gives you a good idea about how an outside-in testing process might look.
Not everyone likes this style of testing. Test double-based testing can be done very badly, resulting in brittle tests that are painful to write and expensive to maintain.
Avdi Grimm has been doing a series of Ruby Tapas screencasts on “Mocking Smells”. In this series, he outlines many of the bad ways of doing mock-based testing and how to do it right. Ruby Tapas is generally awesome and worth subscribing to, but this series in particular is pure gold. Highly recommended.
I find that I can avoid most of the mocking smells by doing the things we’ve talked about here:
Test only the externally-visible behavior of the object; don’t couple the tests to internal implementation details.
If I feel like I need to test internal details, there’s probably an object that is trying to get out. I need to pay attention to that.
If I feel like I have to write a lot of tests using an inconvenient API, that’s also a sign of a design problem. Either the API is wrong, or there’s another object that wants out.
Once I identify the need for a collaborator, I focus on the messages first. I want to find a nice high-level message that communicates what I want done, but not how to do it.
After I identify the message, I think about what object needs to respond to that message. I try not to constrain my thinking to the objects I already have. I’m not scared to introduce new objects if it improves my design.
By following the outside-in approach the way I’ve outlined it here, I gain a number of benefits:
I can focus on one object at a time. I can test a single object’s responsibilities in isolation without worrying about the details of other objects. As the need for other objects arise, I write a quick skeleton of them, but I don’t need to think about them until I get to them.
Once I move down to the next layer, the skeleton classes I’ve built act as a natural to-do list. I know exactly what the public API of these objects needs to be, so I know what I need to test.
It’s much easier to write tests for my objects. I don’t need to do a bunch of complicated setup for the collaborators, and I can easily test weird corner cases and error conditions using mocked or stubbed methods on the collaborators.
I don’t spend a lot of time building up lower-level objects that I end up not needing. When I develop inside-out (or bottom-up), I have some ideas about what I’ll need at the higher levels, but those often turn out to be wrong.
My objects have nice, high-level APIs that specify “what”, not “how”. That makes them very understandable and easy to re-use.
I end up with a lot flexibility. For example, if we decided we wanted to support a number of different formats for the currency list feature, we could turn
ListFormatterinto an interface (or role) and have a number of different implementations. Whatever formatter we like could be plugged into the
CLIobject using the optional keyword parameter we introduced.
I maintain ease-of-use for the common case. The injected constructor parameters are all optional with sensible defaults; the normal user of the
CLIobject does not need to supply any additional parameters.
I end up with a lot of small, single-purpose objects that are easy to understand and easy to wire together in different ways. The Single Responsibility Principle emerges naturally.
If you’re not used to testing this way, I recommend trying it out on a small side project to see what you think.