Testing Patterns: Given-When-Then vs Arrange-Act-Assert

Anecdotally I’ve recently noticed an uptick in presenting the AAA pattern for writing good tests. Most of my career I’ve thought about this from the Given/When/Then paradigm. My assumption much like the dominance of RSpec in Ruby was that was the common explanation/mnemonic when describing how to layout tests. The only other pattern I had a sense of was the old xUnit idea based around its minimalist methods:

  • setup
  • assert
  • teardown

Given/When/Then was such a default that Jim Weyrich even created his own testing framework based on it, rspec-given. It comes out of the BDD movement where the focus was to not get so focused on the mechanics of testing, but more on the value of specifying expectations up front before writing the code to implement the tests. Gherkin sprouted out of this and it was further solidified by being built into tools like Cucumber and RSpec Given.

describe Formatter
  describe "#add_emojis" do
    it "adds emojis to the string" do
      # Given
      formatter = Formatter.new
      string = "Hello, World!"

      # When
      formatted_string = formatter.add_emojis(string)

      # Then
      expect(formatted_string).to eq("👋, 🌎!")
    end
  end
end

Apparently AAA was coined way back in the fairly early XP days by Bill Wake. Reading through his reasoning I appreciate that he was trying to define the steps as:

  1. Arrange -> setup the collaborating object, mocks and construct the object
  2. Act -> actually perform an action usually calling a method or function
  3. Assert -> make the assertions

When actually writing these he mentions that a TDD/BDD approach he favors is actually to sort of reverse the order for writing a test as:

  1. Assert -> write this first because you’re trying to think about what the object should actually do (Test Driven Design)
  2. Arrange -> now you have an idea of what you need to setup the object
  3. Act -> actually call the method/function
describe ShoppingCart do
  describe "#total" do
    it "updates the total when adding a book" do
      # Arrange
      cart = ShoppingCart.new
      keycaps = Product.new(name: "Emoji Keycaps", unit_price: Currency.new(59.99, "USD"))
      switch = Product.new(name: "Clicky Switches", unit_price: Currency.new(0.27, "USD"))

      # Act
      cart.add_item(keycaps)
      75.times do
        cart.add_item(switch)
      end

      # Assert
      expect(cart.total).to eq(80.24)
    end
  end
end

I’m still sold on the nice rhyming structure of Given/When/Then as easy to remember and a little nicer sounding. People often shortcut to the AAA pattern and it becomes easy to forget what it’s trying to remind you of. As I get further in my career I find it encouraging from the early days of TDD where trying to get people to even think about writing a test was a long often fruitless discussion. Nowadays I have time to think about very small aspects of testing like helpful shortcuts to remember the common testing patterns.

Inline setup versus let in RSpec

let has a long history in RSpec having been introduced with a commit in 2009 and rolled out as part of RSpec 2. I adopted it early on as the aesthetics appealed to me with let variables being defined first in little blocks with symbols. One of our engineers wrote a macro in vim to quickly hoist instance variables in older style RSpec tests to let declarations. It became the default style for everyone in RSpec with only a bit of controversy around let! which could be easy to miss in tests. I knew about the lazy initialization, but it was the aesthetics I prized.

I remember a bit of a community debate a few years later with a famous Thoughtbot blog post entitled Let’s Not. It argued pretty convincingly that let was a mystery guest pattern which you don’t want when writng clear tests. Since our team’s default at that point was to use let, but we wrote pretty small Sandi Metz style classes with a single responsibility, the lets weren’t causing a big headache. If the code and the specs were visible in a single editor window, it was easy to see what was going on.

Fast forward to working on one of the largest Ruby codebases in the world and I got reintroduced to the idea of containing the entire test inline. Much of the codebase doesn’t fit in a single editor window and suddenly those little lets are screens away and hard to find. Add with nested contexts, shared examples and the like I was really hating let. Some of the more painful specs potentially executed up to 100 lets before running an individual spec and often fired hundreds of SQL queries. I continued to use it on newer refactored code, but eventually the team started to cut back to allowing fewer lets in a given spec as a compromise. Today my default is to write the entire context of the test inline with no lets at all.

So a let heavy spec like the following is forced into:

RSpec.describe BlogPost do
  let(:author) { Author.new(name: "Gunther Hemingway") }
  let(:editor) { Editor.new(name: "Jordon Adams") }
  let(:category) { Category.new(name: "technology") }
  let(:comments) do
    [
      Comment.new(author: "hank", content: "great post!"),
      Comment.new(author: "lisa", content: "very informative."),
    ]
  end
  let(:tags) { ["ruby", "software development", "tdd"] }
  let(:blog_post) do
    BlogPost.new(
      title: "rspec and let",
      content: "using let in rspec can help with...",
      author: author,
      editor: editor,
      category: category,
      comments: comments,
      tags: tags
    )
  end

  describe "#publish" do
    it "notifies the author and editor when published" do
      blog_post.publish

      expect(author.notifications.last).to eq("your post 'rspec and let' has been published.")
      expect(editor.notifications.last).to eq("the post 'rspec and let' you edited has been published.")
    end
  end
end

This much more explict style:

RSpec.describe BlogPost do
  describe "#publish" do
    it "notifies the author and editor when published" do
      blog_post = BlogPost.new(
        title: "rspec and let",
        content: "using let in rspec can help with...",
        author: Author.new(name: "Gunther Hemingway")
        editor: Editor.new(name: "Jordon Adams")
        category: category.new(name: "technology"),
        comments: [
          comment.new(author: "leslie", content: "great post!"),
          comment.new(author: "bob", content: "very informative.")
        ],
        tags: ["ruby", "software development", "tdd"]
      )

      blog_post.publish

      expect(blog_post.author.notifications.last).to eq("your post 'rspec and let' has been published.")
      expect(blog_post.editor.notifications.last).to eq("the post 'rspec and let' you edited has been published.")
    end
  end
end

Inline default leads to several happy impacts:

  • If you ever need to move this spec, copy it as a starting point, etc, it’s all intact as a single unit
  • Sure you’ll probably duplicate some of this for a second spec, but deep in a spec file you’ll never need to scroll around to see what’s going on
  • If your setup code looks like many lines of boilerplate perhaps your class has way to many dependencies and needs reactoring

So if you haven’t tried this recently I’d invite you to do an experiment for a week and write zero lets.

Interview Tip: Checkout Online Code Environments before the Interview

Live coding is a stressful experience. One of the simplest ways to reduce stress around solving a unknown problem in less than an hour is to setup your environment before the interview. My anecdotal experience is that so few developers do this in practice. As an interviewer its a good sign when you check the coding environment before and see the candidate has already setup the environment and got a test up and running.

It’s a signal to the interviewer of several things in the interview:

  • You are naturally prepared
  • You are able to take full advantage of any tools/libraries provided
  • You are pretty motivated to work for this organization

Using CoderPad as an example:

  • You can login ahead of time into the pad setup for your interview or at least login to CoderPad’s site and try out a sandbox.
  • Select a few of the coding environments you are most comfortable with. Unless you are forced into a specific language always choose your day-to-day language.
  • See what libraries you have access to like RSpec, ActiveSupport in Ruby for example.
  • Look at the settings for including important things like vim/emac support, Intellisense, etc.
  • Get a test running in the environment, so you can TDD right away using your favorite testing framework or at least an available one.
  • Test drive a simple problem in the space like FizzBuzz just to get a feel for the environment and how it works.
  • If this is the actual coding environment for the interview leave your code in there for the interviewer to discover.

I know I see this level of preperation maybe 5-10% of the time, so it’s an easy way to start off strong.

Quick Inline Vim Keymap Shortcut

I often forget the exact syntax for writing a quick shortcut in in vim/neovim. After hunting for several minutes this morning I made a decision to memorialize the approach. Usually it’s for a simple command like running tests in an Elixir mix console:

:nnoremap ,e :!mix test<CR>

So the first n means it applies just to normal mode. The noremap means it won’t follow any other mapping so it’s non-recursive. The ,e is the what the shortcut is mapped to. And finally the :!mix test<CR> puts the mix command into the terminal and hits a carriage return to execute the command.

Aggregate Failures

After working on several large rails codebases in the last 10 years I’ve seen a familiar pattern. Many tests in Rails projects are integration tests because they rely on actual database objects existing. One assertion per test is great rule when you don’t have tens of thousands of specs running. :aggregate-failures allows you to have multiple assertions while still reporting on each failure clearly.

As a bonus it is honored by Rubocop RSpec RSpec/MultipleExpectations. Not sure why this isn’t documented better with Rubocop RSpec. Here is the code within the MultipleExpectations class that enforces the one assert per spec rule:

MultipleExpectations

def on_block(node)
  return unless example?(node)

  return if example_with_aggregate_failures?(node)

  expectations_count = to_enum(:find_expectation, node).count

  return if expectations_count <= max_expectations

  self.max = expectations_count

  flag_example(node, expectation_count: expectations_count)
end

And you don’t need to feel guilty about adding aggregate failures:

  • It speeds up test runs because it doesn’t do multiple setups
  • By default anything that uses ActiveRecord is not a true unit test
  • You still get all the errors if multiple lines fail
  • Speed of test run on any significant Rails project should almost always win