A note on parameterized testing

If your test is in the business of calculating the expected output from the inputs, then your test is duplicating the logic from the code under test.

And there is a reason why too much logic in tests is frowned upon: If you implement the same logic twice, you are prone to repeat the same mistakes in the test which you already made in the real implementation.

for c in [
  Case(socket_type=SOCK_STREAM, fruit="Apple"),
  Case(socket_type=SOCK_STREAM, fruit="Orange"),
  Case(socket_type=SOCK_DGRAM,  fruit="Apple"),
  Case(socket_type=SOCK_DGRAM,  fruit="Orange"),
]:
  # Oh no, surprise logic in the test!
  # This becomes hard to find when the test gets longer.
  expected = 1
  if c.socket_type == SOCK_STREAM and c.fruit == "Orange":
    expected = 0  # Expecting nothing in this case

  self.assertEqual(expected, operation(c.socket_type, c.fruit))

Better is to flatten out the expected results into the test table and remove that logic from the test:

for c in [
  Case(socket_type=SOCK_STREAM, fruit="Apple",  expected=1),
  Case(socket_type=SOCK_STREAM, fruit="Orange", expected=0),  # special case
  Case(socket_type=SOCK_DGRAM,  fruit="Apple",  expected=1),
  Case(socket_type=SOCK_DGRAM,  fruit="Orange", expected=1),
]:
  self.assertEqual(c.expected, operation(c.socket_type, c.fruit))

This is one of these articles that I’m writing just so that I can point to it later.

Comments