Acceptance Tests Trump Unit Tests

At work, we have been practising something approximating Acceptance Test Driven Development now for several months. This means that pretty much every feature of the system that a user would expect to be there, has an automated test to ensure that it really is.

It has given me a whole new perspective on the value of tests as artefacts produced by a project.

I made a pledge to myself when I started this new job in August that I would not (knowingly) check in a single line of code that wasn’t driven out by a failing test. At the time, I thought this would always mean a failing unit test, but I’m starting to see that this isn’t always necessary, or in fact even wise.

Don’t get me wrong. Unit testing is extremely important, and there’s no doubt that practising TDD helps you to write well-structured, low-defect code in an really satisfying manner. But I do feel like the extent to which TDD, at the level of unit testing alone, allows for subsequent changes to the behaviour of the code, has been oversold.

If you think you’re doing TDD, and you’re only writing unit tests, I think you’re doing it wrong.

As new requirements come in, the tensions influencing the design of the code shift. Refactoring eases these tensions, but by definition means that the design has to change. This almost certainly means that some, often significant, portion of the unit tests around that area of the code will have to change too.

I struggled with this for a long time. I had worked hard on those tests, for one thing, and was intuitively resistant to letting go of them. More than that, I knew that somewhere in there, they were testing behaviour that I wanted to preserve: if I threw them out, how would I know it still worked?

Yet those old unit tests were so coupled to the old design that I wanted to change…

Gulliver

In my mind, I have started to picture the tests we write to drive out a system like little strings, each one pulling at the code in a slightly different direction. The sum total of these tensions is, hopefully, the system we want right now.

While these strings are useful to make sure the code doesn’t fall loose and do something unexpected, they can sometimes mean that the code, like Gulliver in the picture above, is to restrained and inflexible to change.

The promise of writing automated tests up front is regression confidence: if every change to the system is covered by a test, then it’s impossible to accidentally reverse that change without being alerted by a failing test. Yet how often do unit tests really give us regression alerts, compared to the number of times they whinge an whine when we simply refactor the design without altering the behaviour at all? Worse still, how often do they fail to let us know when the mocks or stubs for one unit fail to accurately simulate the actual behaviour of that unit?

Enter acceptance tests.

By working at a higher level, acceptance tests give you a number of advantages over unit tests:

  • You get a much larger level of coverage per test
  • You get more space within which to refactor
  • You will test through layers to ensure they integrate correctly
  • They remain valuable even as underlying implementation technology changes

Admittedly, the larger level of coverage per test has a downside: When you get a regression failure, the signpost to the point of failure isn’t as clear. This is where unit tests come in: if you haven’t written any at all yet, you can use something like the saff squeeze to isolate the fault and cover it with a new test.

They’re also much slower to run, which can be important when you’re iterating quickly over changes to a specific part of the system.

To be clear, I’m not advocating that you stop unit testing altogether. I do feel there’s a better balance to strike, though, than forcing yourself to get 100% coverage from unit tests alone. They’re not always the most appropriate tool for the job.

To go back to the metaphor of the pulling strings, I think of acceptance tests as sturdy ropes, anchoring the system to the real world. While sometimes the little strings will need to be cut in order to facilitate a refactoring, the acceptance tests live on.

The main thing is to have the assurance that if you accidentally regress the behaviour of the system, something will let you know. As long as every change you make is driven out by some kind of automated test, be it at the system level or the unit level, I think you’re on the right track.

Agile / Lean Software Development

Comments (2)

Permalink

Is the Value Fetish Killing Agile Teams?

Last weekend I was at CITCON Europe, a great opportunity to meet some of the leading minds in the agile software movement. One intriguing new term I heard a few times was “value fetish”. Let me try to explain what I think it means, and discuss the implications for agile teams.

Continue Reading »

Agile / Lean Software Development

Comments (7)

Permalink

WatiN Goes Cross-Browser

The WatiN (Web Application Testing In .Net) framework, a port of the popular watir framework in ruby, has recently announced support for Firefox. This should make it a compelling alternative to selenium, especially as it looks to be a good deal quicker.

Sweet. Now if only I had a way to serve up an ASP.NET web application from code. Could this be what I need?

Agile / Lean Software Development

Comments (1)

Permalink

Awesome Acceptance Testing

My notes on DanNorth and JoeWalnes‘ session at Spa 2008.

Five artefacts:

  • Automation - the glue that binds the tests to the code
  • Vocabulary - the language that the tests are expressed in
  • Syntax - the technology that the tests are expressed in (C#, Java)
  • Intent - the actual scenario being tested
  • Harness - the thing that runs the tests and tells you if they passed

Four roles. People might fill more than one, or more than one person might be in a role:

  • Stakeholder
  • Analyst
  • Tester
  • Developer

Taking a requirement, the Stakeholder and the Analyst have a conversation:

  • what does that requirement mean?
  • how can we create a shared understanding?

Then the Analyst and the Tester have a conversation:

  • what is the scope of (’bigness’) of this requirement?
  • how will we know when we’re done?
  • => Scenarios (examples)

Tester then ‘monkeyfies’ the scenarios, using the following template:

Given … - assumptions, context in which the scenario occurs.

When … - user action, interaction with the system

Then … - expected outcome

e.g. Given we have an account holder Joe and their current account contains $100 and the interest rate is 10% When Joe withdraws $120 Then Joe’s balance should be $-22

The tester and the developer sit down and write an automated test to implement each scenario.

You might chain these up, but you can always categorise test code into these three partitions. This really helps how you look at test code.

Consistency Validation Between ‘Units’

See the Consumer Driven Contracts paper on Martin Fowler’s website.

Tooling for Automation

Consider extending / creating the domain model to cover the application itself - the UI, the back end.

Loads of tools are availlable. Use whatever works and build on it.

Building a Vocabulary

Ubiquitous Language - Start with a shared language. It becomes ubiquitous when it appears everywhere - documents, code, databases, conversations.

You will use different vocabularies in different bounded contexts. A context might be your problem domain, testing domain, software domain, or the user interface domain.

Beware which roles understand you when you’re talking in a particular domain. Often terms will span domains.

e.g. NHibernateCustomerRepository <– 1 –><– 2–><– 3 –>

1 = 3rd Party Provider Domain 2 = Problem Domain 3 - Software Domain

Make your tests tell a story - make it flow. Don’t hide away things in Setup methods that will make the test hard to read. If that means a little bit of duplication, so be it. ‘Damp not DRY’.

Syntax - Implementing Your Tests

  • write your own
  • keep it simple. don’t fart around writing too fancy a DSL. you’ll be surprised what testers / analysts / stakeholders will be prepared to read.
  • great way to learn
  • Jbehave2
  • training wheels?
  • rspec
  • very nice.
  • create templates for each given / when / then which you can plug together with parameter values into scenarios
  • fit
  • concordion
  • nbehave - joe ocampo

Basically what you need is a way to assemble different permutations and combinations of Given / When / Then with different parameters to make different scenarios.

Expressing Intent

Think in terms of narrative, flow. Think in terms of bounded contexts, and who the audience (role) is for that context. Who will understand that vocabulary?

Make sure the intent is clear - that’s the main thing.

Harness

Do you want to hook into continuous integration build?

Which version of the code is it going to run against?

Keep the tests in two buckets: * in progress * done

Those which are in the ‘done’ bucket, should always work, those which are in progress are allowed to be failing, until you make them pass.

Getting Started

Things you can do today.

  • Try it for your next requirement
  • Given When Then helps guide the tests
  • It’s a collaborative process - get people involved
  • Works for bug fixes
  • a bug is a scenario that you missed in the first place use the tools you’re most comfortable with
  • doesn’t have to be perfect

Down The Line

What to aim for.

  • ALL requirements have acceptance criteria specified up front
  • helps with estimation
  • acceptance tests are automated where appropriate
  • just having thought about it helps - you may come back to automating it later.
  • Push button, availlable to all.
  • helps build trust with stakeholders

Advice

  • Automate pragmatically
  • Don’t try to automate what you can’t do manually
  • Testing is validating an outcome against intention
  • Non functional requirements
  • Plan for false positives
  • Quality is a variable
  • doesn’t mean you don’t go test first
  • doesn’t mean low quality code
  • does mean how complete is the solution? - how many scenarios / edge cases are you going to try and meet?

Summary

  • Have a shared understanding of done
  • There is no Golden Hammer
  • Be aware of the five aspects of test automation
  • Automation, Vocabulary, Syntax, Intent, Harness
  • Start simple, then you can benefit now

Agile / Lean Software Development

Comments (4)

Permalink