The heart of any object-oriented program is the domain model. When we start to build a new system, we like to work directly with the domain model. This allows us to iterate and learn quickly about the problem we’re working on without getting distracted by user interface gizmos. Once we have a domain model that really reflects our understanding of the system, it’s easy to wrap it in a pretty skin.
We’re going to let Cucumber drive our work, building the domain model classes directly in the step definitions. As usual, we start by running cucumber on our scenario to remind us what to do next:
| Feature: Cash Withdrawal |
| |
| Scenario: Successful withdrawal from an account in credit |
| Given I have deposited $100 in my account |
| uninitialized constant Account (NameError) |
| ./features/step_definitions/steps.rb:2 |
| features/cash_withdrawal.feature:3 |
| When I request $20 |
| Then $20 should be dispensed |
| |
| Failing Scenarios: |
| cucumber features/cash_withdrawal.feature:2 |
| |
| 1 scenario (1 failed) |
| 3 steps (1 failed, 2 skipped) |
| 0m0.005s |
When we last worked on this scenario, we’d just reached the point where we had written the regular expressions for each of our step definitions and started to implement the first one. Here’s how our steps file looks:
step_definitions_inside/01/features/step_definitions/steps.rb | |
| Given /^I have deposited $(d+) in my account$/ do |amount| |
| Account.new(amount.to_i) |
| end |
| |
| When /^I request $(d+)$/ do |arg1| |
| pending # express the regexp above with the code you wish you had |
| end |
| |
| Then /^$(d+) should be dispensed$/ do |arg1| |
| pending # express the regexp above with the code you wish you had |
| end |
In that first step definition, we’ve sketched out a call to an imaginary class called Account. Ruby has given us an error that tells us that the next thing we need to do is define the Account class. Let’s go ahead and do that:
step_definitions_inside/02/features/step_definitions/steps.rb | |
| class Account |
| def initialize(amount) |
| end |
| end |
| |
| Given /^I have deposited $(d+) in my account$/ do |amount| |
| Account.new(amount.to_i) |
| end |
Notice that we’re defining the class right here in our steps file. Don’t worry—it’s not going to stay here forever, but it’s most convenient for us to create it right here where we’re working. Once we have a clear idea of how we’re going to work with the class, then we can refactor and move it to a more permanent home. We’re also converting the amount captured from the Gherkin step as a string into a number before we pass it into the domain model.
Let’s run cucumber again and see what it thinks of that:
| Feature: Cash Withdrawal |
| |
| Scenario: Successful withdrawal from an account in credit |
| Given I have deposited $100 in my account |
| When I request $20 |
| TODO (Cucumber::Pending) |
| ./features/step_definitions/steps.rb:13 |
| features/cash_withdrawal.feature:4 |
| Then $20 should be dispensed |
| |
| 1 scenario (1 pending) |
| 3 steps (1 skipped, 1 pending, 1 passed) |
| 0m0.002s |
Well, that was easy! Perhaps...too easy? Let’s review the code in our step definition and see what we think. There are a couple of things we’re not happy about:
There’s some inconsistent language creeping in; the step talks about depositing funds into the account, but the code passes the funds to the Account class’s constructor.
The step is lying to us! It says Given I have deposited $100 in my account, and it’s passed. Yet we know from our implementation that nothing has been deposited anywhere.
Having to convert the amount to an integer is messy. If we have a variable called amount, we should expect it to already be a number of some kind, not the string captured by the regular expression.
We’ll work through each of these points before we move onto the next step in the scenario.
We want to clarify the wording before we do anything else, so let’s think about how we could make the code in the step definition read more like the text in the step. We could go back and reword the step to say something like Given an Account with a balance of $100. In reality, though, the only way that an account would have a balance is if someone deposited funds into it. So, let’s change the way we talk to the domain model inside our step definition to reflect that:
step_definitions_inside/03/features/step_definitions/steps.rb | |
| class Account |
| def deposit(amount) |
| end |
| end |
| |
| Given /^I have deposited $(d+) in my account$/ do |amount| |
| my_account = Account.new |
| my_account.deposit(amount.to_i) |
| end |
That seems better.
There’s something else in the wording that bothers us. In the step, we talk about my account, which implies the existence of a protagonist in the scenario who has a relationship to the account, perhaps a Customer. This is a sign that we’re probably missing a domain concept. However, until we get to a scenario where we have to deal with more than one customer, we’d prefer to keep things simple and focus on designing the fewest classes we need to get this scenario running. So, we’ll park this concern for now.
Now that we’re happier with the interface to our Account class, we can resolve the next issue from our code review. After we’ve deposited the funds in the account, we can check its balance with an assertion:
step_definitions_inside/04/features/step_definitions/steps.rb | |
| Given /^I have deposited $(d+) in my account$/ do |amount| |
| my_account = Account.new |
| my_account.deposit(amount.to_i) |
| my_account.balance.should eq(amount.to_i), |
| "Expected the balance to be #{amount} but it was #{my_account.balance}" |
| end |
We’ve used an RSpec assertion here, but if you prefer another assertion library, feel free to use that. It might seem odd to put an assertion in a Given step, but it communicates to future readers of this code what state we expect the system to be in once the step has run. We’ll need to add a balance method to the Account so that we can run this code:
step_definitions_inside/04/features/step_definitions/steps.rb | |
| class Account |
| def deposit(amount) |
| end |
| |
| def balance |
| end |
| end |
Notice that we’re just sketching out the interface to the class, rather than adding any implementation to it. This way of working is fundamental to outside-in development. We try not to think about how the Account is going to work yet but concentrate on what it should be able to do.
Now when we run the test, we get a nice helpful failure message:
| Feature: Cash Withdrawal |
| |
| Scenario: Successful withdrawal from an account in credit |
| Given I have deposited $100 in my account |
| Expected the balance to be 100 but it was |
| (RSpec::Expectations::ExpectationNotMetError) |
| ./features/step_definitions/steps.rb:15 |
| features/cash_withdrawal.feature:3 |
| When I request $20 |
| Then $20 should be dispensed |
| |
| Failing Scenarios: |
| cucumber features/cash_withdrawal.feature:2 |
| |
| 1 scenario (1 failed) |
| 3 steps (1 failed, 2 skipped) |
| 0m0.003s |
Now our step definition is much more robust, because we know it will sound an alarm bell if it isn’t able to deposit the funds into the account as we’ve asked it to do. Adding assertions to Given and When steps like this means that if there’s ever a regression later in the project, it’s much easier to diagnose because the scenario will fail right where the problem occurs. This technique is most useful when you’re sketching things out; eventually, we’ll probably move this check further down the testing stack into a unit test for the Account and take it out of the step definition.
We’re at a decision point here. We’ve effectively finished implementing our first step definition, but we can’t move on to the next one until we’ve made some changes to the implementation of the Account class so that the step passes.
It’s tempting to pause here, move the Account class into a separate file, and start driving out the behavior we want using unit tests. We’re going to try to resist that temptation for now and stay on the outside of the Account class. If we can get a full tour through the scenario from this perspective, we’ll be more confident in the design of the class’s interface once we do step inside and start implementing it.
So, we’ll make a very simple implementation in the Account class that’s obviously incomplete but is just right enough to make this first step pass. Think of this like putting up scaffolding on a construction site: we’re going to take it down eventually, but it will help things to stand up in the meantime.
Change Account to look like this, and the first step should pass:
step_definitions_inside/05/features/step_definitions/steps.rb | |
| class Account |
| def deposit(amount) |
| @balance = amount |
| end |
| |
| def balance |
| @balance |
| end |
| end |
Good. We still have one issue left on our list, which is the duplication of the calls to to_i. Now that our step is passing, we can do that refactoring with confidence.
18.190.217.253