Behavior-driven development
About two months ago at CDD we decided to start using the RSpec Behavior-driven development (BDD) framework instead of the standard Test::Unit unit-testing library. My initial interest in using RSpec was that it provided “contexts” for a bundle of tests/specifications (hereafter, “specs”), and that seemed a cleaner way to group specifications/tests than throwing everything in one big test class. Our existing Test::Unit test classes were getting very long (some with 60+ test methods if I remember correctly), and related tests were grouped just by placing them next to each other in the file, which wasn’t always maintainable/maintained. And of course, when you have sixty tests in one class, the setup method has to be too general to be used properly. So we needed to do something, and RSpec seemed like it would help. In addition, I liked how specs in RSpec read better than how a Test::Unit assertion reads, i.e. I liked assigns[:assay].should have(4).runs more than assert_equal(4, assigns(:assay).runs.size) and the like.
I have to admit at the time I only vaguely knew what BDD was supposed to be about. The main thing I knew was that BDD was an attempt to change the words we use to talk about automated developer testing/specification/test-driven development (TDD), to make clearer an under-appreciated purpose of such activity, to help developers write code intentionally using better design (loose coupling, etc.). As such, BDD is less a change of practice from TDD (if TDD is practiced correctly) than a clarification of the practice.
After two months using a BDD framework, I have found that while BDD does clarify high-level principles, it still leaves plenty unclear. I have been unit testing for many years now, and I’ve always felt that automated developer testing is a rich subject that takes a long time to fully appreciate, and is not a discipline that can be covered adequately by a few high-level principles. The details matter, because in most realistic testing situations there are always tradeoffs and context-specific considerations that should lead a developer to take one approach over another. That said, BDD is a significant step in the right direction. Over the next week or so I plan to write a series of blog posts examining some of these detailed contexts we’ve encountered at CDD in the context of the principles and tradeoff considerations, with the hope both that these details will be useful to others and that some more experienced BDDers out there will give us some feedback to help us make better choices about how we specify our code.
Before ending this post, I’ll list some of the high-level principles, so I can refer to them over the next week:
- Specs should be valuable.
- Specs should be acceptable.
- Corollary of #2: Some code duplication in specs is ok; the focus should be on clarity/readability/acceptability.
- Specs should specify behavior not implementation (the classic interface vs. implementation distinction). Unfortunately, we’ve discovered that “behavior” is still a fairly vague term (leading to some intense discussions within our team), and what “the interface” is varies according to context.
- Contexts/examples should set up a particular state (of an object, etc.), and specifications should then describe the behavior that state. This is typically accomplished by setting up state in the
setupmethod orbefore(:each)block, and then writing many short descriptions of behavior in separate test methods/specs. - Specs should be loosely coupled to application code, so that refactoring app code doesn’t cause lots of tests to break. There is at least a hope here that by specifying behavior/interfaces you’re likely to get loose coupling as well.
- Specs should encourage developers to think about interface-centric, just-in-time design of their code. This is TDD/BDD’s major benefit #1.
- Finally, I still believe (and here I perhaps depart from some BDDers) that the other major benefit of TDD/BDD is that specs help you verify that your application code works. This is particularly true for small development teams that don’t have a ruthless army of QA people keeping a lid on bugs.
Stay tuned for a specific example later today.