Acceptance Testing

Andy Lindeman

Levels of Testing

Roughly speaking...

  • Unit Tests
  • Integration Tests
  • Acceptance Tests

Unit Tests

  • Isolation
  • Dependencies often mocked or stubbed
  • No filesystem, network, database (Rails "unit" tests)
  • Programmer Tests

Acceptance Tests

A few quick examples...

Unit Test


            describe BankAccount do
              specify "new accounts have a zero balance" do
                account = BankAccount.new
                expect(account.balance).to eq(BigDecimal("0.00"))
              end
            end
          

Acceptance Test

(Cucumber/Gherkin)


            As a depositor
            In order to safeguard my money
            I want to hold it in a bank account

            Given I am a depositor
            When I open a new bank account
            Then my balance is $0.00
          

Acceptance Test


            Given /I am a depositor/ do
              @user = Factory(:depositor)
              sign_in(@user)
            end

            When /I open a new bank account/ do
              click_button "New Account"
            end

            Then /my balance is $([0-9.]+)/ do |bal|
              expect(page).to have_text("Balance: $#{bal}")
            end
          

Workflow

(roughly)

  • Translate a user story into an acceptance test (or few)
  • Watch the acceptance test fail
  • Write a failing unit test
  • Watch the unit test fail
  • Make the unit test pass
  • Refactor
  • Watch the acceptance test get a bit farther
  • Repeat until all tests are passing
  • Confidently deliver the feature

Good Practices

In my very humble opinion ...

Seriously, I'm still learning this stuff too

But here's some things I've observed

Hide Implementation Details


            Given I am a newsletter publisher
            When I send out an electronic newsletter
            Then when Resque jobs are run
            And after 5 minutes passes
            Then the newsletter is received by all recipients
          

Hide Implementation Details


            Given I am a newsletter publisher
            When I send out an electronic newsletter
            Then all recipients receive my newsletter within 5 minutes
          
How would your customer talk to others about this feature? How would a salesperson?

Avoid "Programming"


            Given I am a user named "Alice"
            And there is a user "Bob" who is my friend
            And there is a user "Charlie" who is a friend of "Bob"
            When "Alice" invites "Bob" to a party
            But "Alice" does not invite "Charlie" to the same party
            Then "Alice" receives a message
              "Do you also want to invite Charlie?"
          
No customer or user would ever describe the feature like this ...

Avoid "Programming"


            As a party planner
            In order to have the best party
            I want to invite as many fun people as possible

            Given that I am planning a party
            When I invite my friend Alice
            Then I am asked if I might want to invite her friend Bob
          

Focus


            Given I do not yet have an account with bank.example.com
            When I register for an account
            And I correctly decipher the CAPTCHA image proving
              I am a human
            And I correctly input the code on my RSA SecurID dongle
            And I deposit $20.00
            Then my account balance is $20.00
          
What is this feature really about?

Focus


            Given I am a depositor
            When I deposit $20.00
            Then my account balance is increased by $20.00
          
Some other features should describe that complicated sign up process if it is important.
... but not every feature that implicitly requires signing up.

Use Consistent Terminology


            Given I am a user who has forgotten his password
            When I request that my password be reset
            Then I receive an email with instructions
              to replace my passcode
          
This example is obvious ("password" vs. "passcode") and ("reset" vs. "replace").
... but others are less so. Multiple terms for the same thing are confusing for everyone.
... or might be telling you that the feature is not fully fleshed out yet.

Don't Exhaustively Test Edge Cases


            Given I am a depositor
            And my account balance is $50.00
            When I attempt to withdraw $49.99
            Then the transaction is allowed
            And my balance is $0.01

            Given I am a depositor
            And my account balance is $50.00
            When I attempt to withdraw $50.01
            Then the transaction is not allowed
            And my balance remains $50.00
          
Test many edge cases if they are important, but not as acceptance tests.

But Why?

This is tedious and nobody but me will ever read these.

Your customers can write them

Highgroove - sprint.ly

Your customers can write them

  • They can do this. You may just need to clean up their prose a bit.
  • Now impress them: "the stories you write are being used to verify I implement features correctly"
  • "... and that these features do not break in the future"

My customer doesn't care

Are you sure?

Have you asked them?

Have they seen good acceptance tests in action?

Seriously, they don't care

Admittedly, that's sad

But what if writing them still had benefits?

Other Benefits

  • You as a developer are forced to think as a user
  • ... or a business person
  • "I need this database table and this model and this controller" vs. "what value is this feature providing?" is hard
  • Acceptance tests help formalize that transition

Other Benefits

  • One-stop shop for "what does this application do?" in language everyone can read
  • New developers
  • New project managers
  • Salespeople (oh, we have to know what this thing does to make money?

Other Benefits

  • Who has tried writing real isolated unit tests?
  • It was awesome, right?
  • But did you ever run into issues integrating those isolated objects, esp. after refactoring?
  • Did you have good higher-level tests?
  • ... ones that you trusted and were not brittle?

Tests Should Make You Go Fast

  • Good acceptance tests: have your cake and eat it too.
  • Fast unit tests that drive good design, facilitate refactoring and encourage cohesion.
  • Slower, end-to-end tests that verify everything fits together properly and provides value to customers.

By the way ...

You can write acceptance tests without Cucumber

RSpec and Capybara


            feature "Accounts" do
              include AccountSteps

              scenario "new accounts start with a zero balance" do
                login_as_depositor
                open_new_account
                balance_should_be("0.00")
              end
            end
          

RSpec and Capybara


            module AccountSteps
              def login_as_depositor
                # click_on ...
                # fill_in ...
              end

              def open_new_account
                # ...
              end

              def balance_should_be(balance)
                expect(page).to have_text("Balance: $#{balance}")
              end
            end
          

Be wary of


            feature "Accounts" do
              scenario "transferring money between accounts" do
                visit root_path
                click_on "Login"
                fill_in "username", with: "andy@example.com"
                page.execute_script "$('omg_js').hack()"
                # ...
              end
            end
          

Be wary of

  • Acceptance tests themselves should not change unless the feature itself changed appreciably
  • The code behind may change slightly as the view layer changes

That's all folks