24 May 2017

Deleting Tests

So after my recent post on Refactoring and TDD I had a request to talk a little about keeping your test suite healthy by deleting things. I love to delete things almost as much as I love to create them so I'm up for this discussion in a big way.
Thanks Tim for the request. After starting on this I discovered it was going to be much longer than planned, so deleting tests will have to span a few posts, I hope thats OK. I'll start with some simple stuff around your Unit Tests.

So in the process of keeping your test suite healthy there comes a time when you need to remove some dead-weight. There are a lot of theories and practices out there discussing this topic. I'm going to share my view point. 

First, don't delete unit tests. Properly written unit test should be blazingly fast, thousands of them should run in a few seconds +/- environment startup. So as long as they are still attached to live code, keep them around, they aren't hurting anyone. That said, if you delete code there should be some corresponding failures in your test suite that should cause you to remove some tests, or parts of some tests. Don't leave the test code behind, delete it along with the source it was attached to; ideally in the same commit so that the changes go together in and out of your source control system. 

That sounds easy, but in practice it can be a bit of a challenge. When production source goes away, it should be going away because some test is telling it to go away. That is, a test that once demanded an action occur, no longer demands that action. 

For example, 

This silly example does three calculations, but lets pretend that in the future we don't care about mass, so we need to remove from the result set, if we're really test driving, what does our code look like?

We could test the resultant structure to ensure that mass no longer appears in the output. First, lets add a test to show the structure of the result of calculate_all;

This test would pass of course, but if we removed the :mass key from the assertion it would start failing. In order to make it pass we would then remove :mass from the result of calculate_all and that would cause the validation of the mass calculation to fail, causing us to remove that test as well.

Our final result might look like this,

The point is, we got rid of some code and we did it in a test driven way. Why would we expend all this extra effort? To ensure that we removed only what we intended to remove and nothing more. 

OK you say, that's easy, but what about more intricate constructs, what about Integration Level Tests, or Acceptance Tests? I submit to you that the process is the same, only there are many more moving parts. 


PS -- I'd like to point out a few things about my super-ultra contrived example. One, I don't generally advocate for static/class methods, but it made this example a little cleaner. Two, I wouldn't necessarily advocate for the implementation of the calculate_all function, it does too many things, but again, for clarity I mashed it all together. Lastly, and probably most interesting to me is, what is the validity of the test_calculate_all_return_structure? Should that test have always been there? Maybe. Probably. I think, in retrospect, if I had taken more time to develop this example I would have put it there in the first place and not introduced it later. That said, the test implies something about the structure of the result that may or may not be important. All of this has me thinking about other topics, like should tests imply something about the structure of a result, especially in dynamic languages like Ruby. I guess we can talk about that soon.

No comments:

Post a Comment

Note: Only a member of this blog may post a comment.