Sketching Out the Domain Model

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.

Getting the Words Right

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.

Telling the Truth

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.

Doing the Simplest Thing

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.

images/step-definitions-inside-decision.png

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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.190.217.253