Alright, so after a decade-plus in Ruby, I’ve gotten into some great debates about testing with colleagues. Through these conversations I’ve come up with a testing style that’s not exactly textbook, and it’s been doing the job.
Every test I write has four steps in one block:
it 'provides a succinct description of the behavior' do
setup
exercise
verify
teardown
end
Setup builds what the test needs. Exercise calls the thing under test. Verify asserts. Teardown puts back whatever has to be put back, and it’s often empty. Blank lines between the steps, so the shape is visible before you read a word:
it 'applies the coupon once' do
cart = Cart.new
cart.add(price: 100)
cart.add_coupon('TEN')
cart.add_coupon('TEN')
total = cart.total
expect(total).to eq(90)
end
Each test is verbose enough to read on its own, and none of them share global state.
Verbosity is the part my colleagues argued with, so here’s the trade. A verbose test repeats setup that a helper could have hidden. In exchange, the test tells you what it needs without sending you to another file, and it fails for exactly one reason. When a test breaks two years later, the person reading it is a stranger.
Shared state buys the opposite deal. Extract the setup into a let, a factory, or a before block used by thirty tests, and every one of those tests now depends on a decision made somewhere else. Change it for one test and you break the other twenty-nine. The setup grows to serve every case, until nobody can say what any single test is exercising.
Keeping all four steps in one block is what makes a test readable on its own. A test you can read top to bottom is a test you can delete, move, or rewrite without checking what else it touches.
It’s also the basis of a fast suite. Tests that share nothing can run in parallel, spread across every core you have, in any order. The moment two tests lean on the same setup or the same row, they have to run one after the other, and the suite is only as fast as that queue.
Ruby culture treats DRY as a virtue, and the test suite is where I break it on purpose. Duplicated setup costs a few extra lines. Coupled tests cost you every time one of them changes.