Saturday, February 18, 2017

Micro-ATDD

I strive to make all my tests be both microtests and acceptance tests, an idea I learned from Arlo Belshee.

When I say this to people, they are usually confused first, then doubtful when I explain. I don't think I'm ready to address the doubt, but maybe I can address the confusion today.

Microtests

Coined by GeePaw Hill (see his article for his original definition), a microtest is like a unit test, but it has all the qualities I wish all unit tests had. It's fast and focused. It answers the question "does this little piece of code do what I intend it to do?"

Because microtests talk directly to the system under test, they are written in terms of the SUT.

It's obvious that a microtest can only be used on parts of the code that are simple and decoupled and isolated. An integration test is never a microtest.

Acceptance Test

An acceptance test describes expected software behaviors from the point of view of a user or other stakeholder. It answers the question "does this system meet the requirements I expect of to meet?"

Because acceptance tests are written in conversation with that user, they are written in the language of that user. They are organized like a spec.

My ideal tests

I want tests that hold to all of the above. My ideal tests are super fast, 100% reliable, simple, isolated, written in the language of the user, and easy to read. (This requires the SUT to be decoupled, cohesive, well-named, and DRY - properties I already want.)

Every test is both an acceptance test and a microtest.

The doubt

The usual objection I hear is "while isolated unit tests tell you about each of the little pieces, you still need some kind of integration test to confirm that all the parts work when you put them together." 

Well, that's true for most programs, but it's only necessary because of how your code is organized. "parts work when you put them together" means "the desired behaviors of the program (the acceptance criteria) are emergent properties of the system". But we know how to refactor. If two parts of the system need to work together, we can put them together in the code, and then use a microtest to assert that desired behavior.

2 comments:

Steve Meredith said...

> we can put them together in the code,

What does this mean?

Jay Bazuzi said...

Find the bit in component A that must agree with some bit in component B. Instead of using an integration test over A and B, pull those bits out and put them together as component C, which you can test in isolation.

This decouples A from B. It leaves you with coupling between A and C, and B and C, but the trick is to make that a lower form of coupling (e.g. eventing instead of a direct function call).