on behavioural testing

I’m back after a long spell away from blogging, having settled into a new work position and having had a few personal code retreats I recently talked about.

In that time I wanted to explore what it means to unit test and acceptance test and came to a few conclusions that I wanted to share.

Test driven development

Years back when I first started test driven development it was a revelation – the control it gave you over your own code, the confidence in what you were delivering was amazing. I started with stubs, test spies and all kinds of funky hand rolled stuff, but soon found that mocking frameworks enabled you to gain stubs for free using their proxies. This is when the testing began to naturally morph into interaction testing – by testing the relationships between single responsibility objects I had full control over my code and could edge case everything.

This is where I think a lot of people get to, having read debates about state vs. behavioural testing and a few blogs where great developers admit that “I made a mistake and got too involved in interaction testing”.
Some of us who have been through this recognise the brittle unit tests that come with behavioural unit testing, however it is not black and white that this brittleness is a bad thing.

I specialise in helping teams become more agile and teach test driven development, and more often than not the environment that I enter is one where unit testing gets done, but test driven does not. And there are only 150 tests on a huge code base.
I find that a natural fit for the developers who want to get started on TDD with this code base, which is often huge classes with mangled responsibilities and little dependency injection, is to go with the tried and tested way of pulling out single responsibility behaviour using a test driven practice and moving the code out piece by piece, introducing dependency injection and abstractions where appropriate.
This then naturally encourages behavioural interaction using mocking frameworks for stubs because they want to ensure that these new SRP pieces speak to each other in the right way and that less TDD thinking developers don’t rip it out.
They embrace potentially brittle tests with the knowledge that they might need changing or reworking in the future in exchange for a higher level of confidence that the behaviour of our code is correct. More importantly their code base becomes testable.
Provided this is a conscious decision, I think it can have great benefits.

Acceptance test development

In an ideal world, we would start a clean project with acceptance testing up front, or rather Specifications By Example. By driving features that examine how and what the product owner/customer sees in our application we can free ourselves up from a lot of the internal behavioural testing. We potentially then unit test where there is complexity or edge cases and let the acceptance tests cover the rest when we want to.

I’m working on a project at the moment that falls somewhere between the two – there are unit tests, we are introducing dependency injection and splitting responsibilities, yet we already recognise the value of acceptance tests. More importantly, this end to end feature testing is very possible with some internal changes to the code and some abstractions. These higher level tests will free us from the test per class restraint we often find ourselves in while test driving a legacy code base.

To conclude I think that test per class and behavioural testing is not a bad thing at all, just that if you can there are better ways to reduce brittleness and test rework.



0 comments: