by Leon Rosenshein

Properties

Properties are more than squares on a Monopoly™ board. According to dictionary.com, a property is

  1. that which a person owns; the possession or possessions of a particular owner:
  2. goods, land, etc., considered as possessions:
  3. a piece of land or real estate:
  4. ownership; right of possession, enjoyment, or disposal of anything, especially of something tangible:
  5. something at the disposal of a person, a group of persons, or the community or public:
  6. an essential or distinctive attribute or quality of a thing:

Monopoly uses the first 5 definitions, but I’ve been thinking about the sixth version. Properties as attributes and how they relate to software in general and testing specifically. I’m a believer in unit testing. I like the confidence it gives me that what I’ve written does what I think it does. I like that it tells me that the public surface of the thing I’m writing makes sense. That it doesn’t surprise anyone or make them work too hard. And I try to test before as much as I can, but sometimes I end up testing after.

Regardless of when I test though, my first inclination is to think of edge cases and write specific cases for them. That works, but it leaves you vulnerable to the Enterprise Developer From Hell (EDFH). The EDFH is the kind of person who will write code designed to pass your tests, but not actually be correct. The kind of person that would write code that looks at the inputs by running your tests and set the output based on pattern matching. At some point you might convince the EDFH to actually write the code you want, but if you only have a handful of inputs then it’s easier and quicker for them to just pattern match.

One way around this is to use property-based testing. Take something simple like classifying triangles given the length of the three sides. One way set of classifications of triangles is this set of 4 types:

  • equilateral: All three sides are the same length
  • isosceles: Two sides are the same length
  • right: The sum of the square of the length of 2 sides is equal to the square of the length of the third
  • regular: Any other triangle

A conscientious developer would do some analysis of the requirements, right some if statements that compare the sides, and then return the correct value. The EDFH, on the other hand would run your tests, see that your inputs/expected results are

  • 2, 4, 6 -> regular
  • 1, 1, 2 -> isosceles
  • 3, 4, 5 -> right
  • 7, 7, 7 -> equilateral

And right a function who’s psuedo-code looks a lot like

switch (inputs) {
  case 1, 1, 2 : return "isosceles"
  case 3, 4, 5 : return "right"
  case 7, 7, 7 : return "equilateral"
}
return "regular"

Sure, the tests will pass, but it’s not right. The question is, is there a way to get around the EDFH? One way is to use property based testing.

You know the properties of the different types of triangles. You also know a couple of other properties of triangles in general you can use to your advantage. You know the order of the sides doesn’t matter. You know that the sum of the lengths of any two sides must be greater than the length of the third side. So, instead of hard-coding the lengths, pick two of them randomly and then set the 3rd based on the known properties of triangles. Then try all the different permutations of the order of the lengths you’re validating. They should all give the same correct results.

When you use the properties to validate the function you make things much harder for the EDFH. They can’t do specific pattern matching. They’ll have to fall back to writing code that actually uses the properties, which is much more likely to result in code that actually does what you want, not just passes all the tests.

Property based testing has value even if you’re not dealing with the EDFH. It can find gaps in your logic. There’s one case we haven’t talked about yet. What happens when your input breaks the rules. Sure, you said “Tell me what kind of triangle this is”, and defined what 3 of the types were rigorously, but the 4th type was an incorrect catch-all. Tthe input is pretty open ended. You can use the properties to write a test that breaks the side length rule. Inputs that aren’t even a triangle. If you pass in 3, 4, and 10 the developer should come back to you and ask what you want done if the sides don’t represent a triangle.

So when you write tests, even if you know the developer isn’t the EDFH, because you’re not just the test writer, but the developer as well, think about the properties you’re really testing.