/ Ollivander

One Assertion Per Test Considered Ambiguous

There's a practice of good programming which is often referred as One Assertion Per Test.
In the opinion of some people, this practice would advocate the usage of a single call to xunit assert method for each test.

I think that idea is plainly wrong. Saying "One Test Per Test" would have been better, but implied an high tautology risk.

Let's take an old-and-trite shopping cart example; we have a cart which the user fills, then a checkout happens and we have a completed order, which holds things like a total amount for the order and a list of items.

Sometimes people will complain about this (Python is used throughout the examples, yet the concept applies to any language):


def test_successful_checkout(self):
    item1 = Item(price=10.00)
    item2 = Item(price=20.00)
    cart = Cart()
    cart.add(item1)
    cart.add(item2)
    completed_order = Checkout.perform(cart)
    self.assertEquals(30.00, completed_order.amount)
    self.assertEquals([item1, item2], completed_order.items)

(I know floats aren't good for currency. I just didn't want to make the example too long.)

and yet would find the following completely acceptable:


def test_successful_checkout(self):
    item1 = Item(price=10.00)
    item2 = Item(price=20.00)
    cart = Cart()
    cart.add(item1)
    cart.add(item2)
    completed_order = Checkout.perform(cart)
    expected_order = CompletedOrder(amount=30.00, items=[item1, item2])
    self.assertEquals(expected_order, completed_order)

Do you see? One assertion per test.

{% img center /images/download.jpeg %}

Frankly speaking, that's bullshit. One assertion per test does not mean that. One assertion per test means test ONE THING for each of your tests.
You should begin thinking about whether your object does too many things and whether your test methods are properly named.

A possible example would be:


def setUp(self):
    item1 = self.item1 = Item(price=10.00)
    item2 = self.item2 = Item(price=20.00)
    cart = Cart()
    cart.add(item1)
    cart.add(item2)
    self.completed_order = Checkout.perform(cart)

def test_successful_checkout_order_amount_is_the_sum_of_item_prices(self):
    self.assertEquals(30.00, self.completed_order.amount)

def test_successful_checkout_order_items_are_items_from_the_cart(self):
    self.assertEquals([self.item1, self.item2], self.completed_order.items)

Here, since the checkout process is doing multiple things (and it probably should as it is a checkout - this is probably more an example of an acceptance test rather than a unit test), we test them separately. If either fails, we know why and can go straight to the issue - and yet consider rewriting the second test like that:


def test_successful_checkout_order_items_are_items_from_the_cart(self):
    self.assertEquals(self.item1, self.completed_order.items[0])
    self.assertEquals(self.item2, self.completed_order.items[1])

If the example above is good, that's good as well, and you may have reasons for doing things like that - maybe your test framework doesn't show you the differences in a very clear way and you want to be sure to quickly catch the wrong item. This is what Robert C. Martin calls Single Concept per Test in his own Clean Code book - a recommended reading!

So, "You're testing multiple things in a single test" can be a good remark. "You're using multiple assertions per test", on the contrary, means nothing.