05 June 2017

Test Pyramid Inversion

So I was talking to Schmonz about Redundancy in the test suite the other day. I asked him for some examples, trying to loosen the grit in my brain, help me see what I couldn't see. He put forth a number of good examples/illustrations of what he sees as redundancy/smells in test suites. Very helpful guy that Schmonz. 

I'm going to tackle something that he triggered with his first response. 'lots of acceptance tests instead of lots of unit tests, and the acceptance tests naturally have lots of overlap,'

That sounds like Test Pyramid Inversion to me!

When I think about code and TDD in particular, I'm drawn to the notion of making all the parts right and then putting the parts together well. Unit Testing is my assurance that all the parts are right. Or rather, all the parts have an expected, predictable, and tested behavior. At a minimum, when they behave a certain way I should not be shocked. But TDD as a general principle goes beyond that. 

Above my layer of well formed parts I can have more layers of tests. Tests that tell me how collections of units of code work together. I call these integration tests, I've heard a dozen names for them, but one way or another, if I glue several atomic units of code together under one test, it escapes the 'microtest' paradigm and becomes something else. Those tests must test things that have been tested before. Does that make them redundant? I say no.

I don't see these things as redundant because at the microtest level all I'm really doing is proving that the code under test behaves in a certain way. I'm not saying anything about how it might interact with other interesting things in the system. An example of this (real world analog) might be something like a screwdriver. 

A screwdriver is supposed to do only two things, put screws in and take them back out. So realistically it might have three or four tests. Say, in_with_screw, out_with_screw, in_without_screw, out_without_screw. There are no tests for used_as_chisel or used_as_crowbar. That should tell you something right there. This object was not meant to integrate with hammer or fulcrum. So you shouldn't be surprised when it doesn't work well as a chisel or crowbar. 

Now, that said, we are all prone to tool abuse. I've certainly used my screwdriver as a hole punch or a crowbar or something and you probably have too. So lets say you are building a Rube Goldberg Machine and you decide to use a screwdriver as a weight on a string to turn on a fan. That is not the intended use of the screwdriver. But if you have a Test Driven Rube Goldberg Machine, you might write a higher level test that checks that the screwdriver is the right weight, length, etc. to behave as you need it to. This is not redundancy in the test suite (yet). 

Lets say you then choose to tape a screwdriver onto the shaft of the fan and use that screwdriver to turn a long screw that moves a marble up an inclined plane. Thats like what a screw driver does, it turns screws. We have 'in_with_screw' to show us that. Is this test redundant? Its the same thing isn't it? 

Its not the same thing. One, the screw is not pulling itself into a block of material (or a block of material toward it). Two, the condition we would assert to be true is something along 'marble moves up the incline plane' not 'screw disappears into wood'. So its not redundant at all. It is a matter of perspective. 

So given this assertion that redundancy is a matter of perspective I probably need to go think up things that are genuinely redundant and draw some examples from that. I have a story I'll share on the topic in an upcoming post.