Parameterized testing: You want Inputs and Outputs

Is your test in the business of calculating the expected output from the inputs?

Then your test is duplicating the logic from the code under test.

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))

A non-trivial real-world example (from the Go-Landlock library).

provided inputs system under test expected outputs you want both inputs and outputs in the test table

This is one of these articles that I’m writing just so that I can point to it later. In a code review. :)

Comments