DevOps Zone is brought to you in partnership with:

I am a programmer and architect (the kind that writes code) with a focus on testing and open source; I maintain the PHPUnit_Selenium project. I believe programming is one of the hardest and most beautiful jobs in the world. Giorgio is a DZone MVB and is not an employee of DZone and has posted 638 posts at DZone. You can read more from them at their website. View Full User Profile

Acceptance Test-Driven Development

03.11.2010
| 16861 views |
  • submit to reddit

I am halfway through reading Growing object-oriented software, guided by tests, a book that teaches Test-Driven Development in a Java environment. A review will come soon, since the process described in this work is really language-agnostic and interesting also for php developers.
However, the book's authors introduce a very productive practice, which consists in a double cycle of TDD:

  • a longer cycle, where you write acceptance (aka end-to-end) tests, deriving them from the user stories or formal requirements, and make them pass;
  • a shorter cycle contained in the first, which happens in the phase when an acceptance test is red: you write unit tests and make them pass until the related acceptance test does not fail anymore.

                                          

    This approach is an implementation of Acceptance Test-Driven Development, and in particular makes you write several unit tests for every acceptance test (read for every feature) you want to add. Acceptance testing gives immediate feedback on the application's external qualities: simplicity of use and setup, consistency of the interface. At the same time, unit testing gives feedback on the internal qualities: decoupling, cohesion, encapsulation.
    When I started employing the double cycle, getting in the zone suddenly became less difficult. The advantages of the TDD process were for the first time applied to the whole process, from the requirements formalization to the end of a feature's development:
  • test-first paradigm. By the end of the development phase, regression tests will be already in place, and the production code will be forced to be testable.
  • The definition of "done" is very clear (the acceptance test passes), and you are more likely to write only the mandatory code to get a green bar at the higher level.
  • measuring progress is easy: the number of acceptance tests that are satisfied (weighted by points). You can even write a set of acceptance tests for the whole iteration in advance and keep them in a separate suite, moving them in the main suite when they start to pass.

To be a bit more specific, the php technologies I use for the two cycles of development are Zend_Test for the acceptance tests suite and plain old PHPUnit test cases for the unit tests one.
Zend_Test is an extension to PHPUnit that lets me define a contract for the http interface of a Zend Framework application, assert redirects, check parts of the html response via css selectors, and even falsify request headers to simulate ajax requests. The unit tests usually have no dependencies on a particular infrastructure, so PHPUnit itself is a powerful enough tool to write them with.
By the way, triting an automated acceptance test suite is more difficult than writing unit tests, as there is more infrastructure that gets in the way and a large amount of details that may render the tests brittle. Fortunately Zend_Test takes care of almost all the infrastructure (aside from the database, which I reset in the setUp phase of test cases), and acceptance tests code can and should be refactored to avoid duplication of the implementation details. For instance, Css selectors used to assert on parts of the html response can be stored in constants with an expressive name, and the request creation can be wrapped in helper methods that are composed in actual test ones. Also custom made assertions are helpful in keeping the noise to a minimum.

I hope this technique will be useful for all test-infected developers. It certainly enhanced my productivity and will to get a green bar. :)
References
Published at DZone with permission of Giorgio Sironi, author and DZone MVB. (source)

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)

Comments

Josh Marotti replied on Thu, 2010/03/11 - 10:59am

when you say "write a unit test" what you are probably saying is "write an integration test with *unit" (JUnit, PHPUnit, NUnit, etc...).

 

Remember, a unit test ONLY tests a SINGLE unit.  If I write a JUnit test that calls a DAO that calls a DB, it is an integration test, because I've introduced a second layer (the DB).  If I write a JUnit test that calls a business logic method that calls a dao (but the DB is stubbed out), it is STILL an integration test.  Why?  Because I'm running a test on the BL layer, but introducing the DAO layer (even though the DB is stubbed out).  A true unit test is a test that runs a method on the BL layer, with the DAO layer stubbed out.  That way, the test is ONLY the BL call.


After that, I'd say that acceptance tests and unit tests are two very separate and somewhat unrelated practices.


I tend to write unit tests as I code, then write integration tests (in a whole separate package than my unit tests) that test the requirement, use case, or user story.  They both have an end to the same mean, and can be written in parallel, but I don't like the idea of tying them together...

 

Just my thoughts.

Giorgio Sironi replied on Thu, 2010/03/11 - 4:04pm in response to: Josh Marotti

You're right about *unit* testing, and in fact writing real unit tests is exactly what I mean.

The idea is starting with an acceptance test that will define when you're done with the code, and then with the unit test of the component at the highest-level, stubbing out its collaborators. Then you can repeat the process with the collaborators, and so on. When you're done with single units, you link them together in a factory or DI container and make the acceptance/whole_integration test pass. Of course you can introduce middle-scale tests (functional) that test different object graphs, if that makes you locate integration problems faster.

László Jánszky replied on Sat, 2014/04/26 - 9:02am

 I don't get it. As far as I know acceptance test means that the user tests the final product whether it meets her requirements or not. What you meant was system integration test or end to end test in other words. And the list of tests can be much longer:

user story (examples of how the system should work) -> use cases (system rules) -> e2e tests (system wide integration tests inspired by the use cases) -> integration tests against component relations -> integration tests against module relations -> integration tests against class relations -> unit tests against individual classes

Btw. I never tried to develop a project like this, I am too lazy to write e2e tests, but now with nightwatch.js it seems to be an easy task... Maybe I'll try it out...

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.