Context Specification is a lot more than merely a new way to write tests. Although it was conceived around the same time as Behavior-Driven Development (BDD), and probably inspired by some of the BDD concepts, it is quite different.
The difference between Context Specification and BDD lies in semantics and purpose. Both are concerned about driving development through behaviors rather than implementation artifacts like classes or components.
BDD frameworks are typically purposed to acceptance testing. Recently, programmers encouraged customers to participate in requirements specification by writing the acceptance tests with tools like FIT. Acceptance testing frameworks are built with the assumption that business experts will be writing tests.
Acceptance testing frameworks often presume the user experience will be as rich as it is for the programmer. However, a programmer’s notion of readability and elegance is usually quote different than a non-programmer’s notion of readability and elegance. Often, programmers’ notions of readability and elegance will be different among themselves.
The Given/When/Then grammar is a good example of programmer’s presumption of a good user experience. We have to remember that when we create a tool to be used by non-programmers, we are designing user interaction and user experience. Programmers are historically the least effective people in creating a good user experience.
Given/When/Then grammar is good for capturing all of the details of a given scenario, but chooses to convey a lot of information in prose. Using prose for this purpose often leads to run-on sentences that defeat effective communication.
Specifications written with the Given/When/Then grammar are just as likely to be hard to read. The grammar can be an obstruction to learning, communication, and understanding.
Creating descriptions of specific uses of software is Context Specification’s primary concern. As with BDD and Test-Driven Development, the descriptions are specifications that end up driving development, and leave behind an automated test suite.
Who Writes Specifications? Who Writes Tests?
Programmer’s don’t like to write documentation, and business experts don’t like to write tests. Context Specification is about creating a specifications document with code. However, it doesn’t presume business experts will be writing the tests. Programmer’s write the test; that’s what they’re good at; business experts are good at being business experts.
Context Specification’s semantics focus on transforming traditional tests into specifications. These specifications serve as descriptions and documentation for how a system behaves under the various situations that the system is put into. Context Specification grammar is designed to make specifications easy to read and understand.
The goal is to create specifications that can be understood by anyone. If defining specifications for business experts, the business experts should be able to understand the specifications that you are writing. The implementation of the specifications in code should be easy to understand by programmers.
Context is a word used to describe the situations and conditions a system is put into by a user. Context is often not fresh on the minds of programmers when writing tests because we’re often too caught up in thinking of the technical aspects of the problem.
Focusing on the programming language and the tools around them can distract programmers from context. Scott Bellware has spoken several times about this concept, and his work is my motivation for this article.
We need to do our best to be aware of context as we write tests, or specifications. We need to avoid thinking like programmers when writing and programming specifications. Context Specification can help us with this thinking.
The Context Specification Mindset
Scott Bellware describes the proper Context Specification mindset to have by stating, “Specify the experience, not the implementation.”
Instead of thinking about technical terms and programming ideas like classes, methods, and modules when writing test, consider the subject of the test in a particular context that has a related set of side-effects that you can observe. The observable side-effects are called specifications in Context Specification grammar.
The test suite isn’t really organized around specific abstractions in your system, unless the abstractions are organized around system behavior instead of data. Subjects and context can describe one class or several classes. Specifications reflect the observable effects of one method or several methods.
Subjects, contexts, and specifications show the true value of the code you are creating from the perspective of business objectives rather than implementation details. If you write specifications for subjects within a context, you end up writing code for what is truly important within your domain.
Example Context Specification Thought Process
There are a number of frameworks and methods to practice Context Specification with. In the .NET world, SpecUnit.NET, MSpec, Specter, or simply NUnit with some syntactic sugar can be used to write specifications.
While these are very functional frameworks, I believe the Ruby RSpec framework is the most elegant and easiest to use. Therefore, to make things simpler I will be writing all of my specifications using RSpec and all of my production code in Ruby.
(I intend to write an article on how to use IronRuby with RSpec to test .NET code; stay tuned)
Let’s use an iPod as an example since most people are familiar with how iPods function at a basic level. While we write specifications we will remain aware of the context by not performing a technical dissection of the problem.
Context Specification grammar requires a subject, context, and observable conditions. In this example, the subject is an iPod. One of the contexts could be, “when playing.”
What conditions can you observe when an iPod is playing? Without Context Specification, the answer depends on who is answering the question. If a programmer is answering the question, you will get a very technical answer. If a layman is answering the question, you will get a more general answer.
The laymen might say, “when an iPod is playing, it should display the track name, artist, and album name; it should also play the track.”
Conversely, a programmer might say, “when an iPod is playing, it should find the audio file in the audio file database, load it into the audio player, and send a signal to the audio player to start playing the audio track, update the LCD to re-draw the screen with the appropriate information,” and this can go further into more technical dissection. The programmer’s dialog is hyper-descriptive.
However, with Context Specification, the specifications should be defined in a way so that almost anyone can understand them. Because the layman understands the context in a more general circumstance, their specifications are more acceptable.
Typically, the best person to work with when defining specifications is the person who knows the specifics of how a system should behave, without necessarily knowing how the technical aspects should be woven together. A product owner is a good example of this.
Product owners know how their product helps their business, and how it may help their customers. However, they really don’t care about implementation details, such as the usage of a producer-consumer pattern to manage I/O requests, or adhering to SOLID principles.
Defining a Specification
Let’s start writing specifications for when an iPod is playing. One of the more obvious specifications, as stated from the laymen’s quote above, is “it should display the track name.”
Using RSpec, here is the code that defines this specification:
1: require 'spec'2:3: describe "iPod", "when playing" do4: it "should display the track name"5: end
Now let’s look at the RSpec output from this specification:
*Pending:iPod when playing should display the track name (Not Yet Implemented)./ipod_spec_definition.rb:4Finished in 0.015 seconds1 example, 0 failures, 1 pending
RSpec informs us that there is 1 example and 1 pending, because we haven’t written the implementation of the specification yet. Notice the readable output? RSpec can generate a nifty HTML document with all of the specifications for reference. This can be helpful to business experts for verifying the correctness of specifications.
It is important to note that the context is typically in the gerund form of a verb (“ing” form, e.g “when running,” “when drinking,” etc.) This eliminates accidental communication of timing or synchronization factors to the reader.
For example, the context “when beginning to run” is irrelevant because it is inclusive in the context “when running.” The same is true for “when finished running” because that statement occurs “when running” as well. Programmers might not immediately understand this logic, but from a communication mindset and for the purposes of documentation it conveys the appropriate intent.
Implementing a Specification
Let’s start implementing the first specification: “iPod when playing should display the track name.”
Following traditional TDD practice, the implementation starts by building a skeleton of the code required for the compiler and with a failing test:
1: require 'spec'2: require 'not_a_mock'3:4: class Ipod5: def play6: end7:8: def display_track_name9: end10: end11:12: describe Ipod, "when playing" do13: before :each do14: @ipod = Ipod.new15: @ipod.track_methods16: @ipod.play17: end18:19: it "should display the track name" do20: @ipod.should have_received(:display_track_name)21: end22: end
The implementation of the first specification is performed by providing a body to the specification we are trying to implement, asserting that the iPod should received the display_track_name method.
Since the instance variable “ipod” does not exist, the “before” block is added to create a new instance of an “Ipod” class and assigns it to the instance variable needed. The “before” block is run before each specification is run.
Since all specifications will require an instance of an Ipod, this block makes writing future specifications convenient and further clarifies what subject the specifications are being written for (or more traditionally, the “system under test”).
In order for the Ruby compiler to accept this code, we have to provide a skeleton Ipod class and define the methods that are being called within the specification and the “before” block (“play” and “display_track_name”).
Note that I’ve elected to use a mocking framework called NotAMock. NotAMock is an extension to RSpec that records all calls on an object in question. I’ve further extended the framework so that you can call “track_methods” without specifying arguments to the method. Previously, you would have to specify each method you wanted to track. My change has been merged to the trunk and can be found here.
After running the new changes in the RSpec runner, the output is:
F1)'Ipod when playing should display the track name' FAILED#<Ipod:0x2b90c24> didn't receive display_track_name./ipod_spec_implementation_failing.rb:20:Finished in 0.094 seconds1 example, 1 failure
Notice the test failed with the message “#<Ipod:0x2b90c24> didn't receive display_track_name". RSpec is reporting that the “display_track_name” method on the instance of the Ipod class was never called.
Now it’s time to execute the next traditional step of TDD: make the test pass with the least amount of work. Adding a call to the display_track_name method inside the play method satisfies the RSpec test. Re-running the RSpec runner will show that the test indeed passes without error.
Now, obviously simply adding a call to the display_track_name method within the play method is an incorrect implementation. Therefore, we are lead to the final phase of traditional TDD: refactor:
1: require 'spec'2: require 'not_a_mock'3:4: class TrackRepository5: def self.get_random_track_set6: # query database to return random track set7: [ "track1", "track2", "track3" ]8: end9: end10:11: class Lcd12: def display_track_name(track_name)13: # interface with hardware to display track name14: end15: end16:17: class Playset18: attr_reader :current_track_name19:20: def self.create_random_playset21: tracks = TrackRepository.get_random_track_set22: playset = Playset.new(tracks)23: end24:25: def initialize(tracks)26: @tracks = tracks27: @current_track_name = @tracks28: end29: end30:31: class Ipod32: def initialize33: @lcd = Lcd.new34: @current_play_set = Playset.create_random_playset35: end36:37: def play38: display_track_name(@current_play_set.current_track_name)39: end40:41: def display_track_name(track_name)42: @lcd.display_track_name(track_name)43: end44: end45:46: describe Ipod, "when playing" do47: before :each do48: @ipod = Ipod.new49: @ipod.track_methods50: @ipod.play51: end52:53: it "should display the track name" do54: @ipod.should have_received(:display_track_name)55: end56: end
The “play” method now asks the “current_play_set” instance variable what the current track name is and provides it to the “display_track_name” method. This leads to creating the “current_play_set” instance variable, the “Playset” class, and a few other classes and methods.
After running the RSpec test runner again, the above code satisfies the test without issues. We know that we’ve safely refactored our code to a decent implementation without changing any behavior.
It’s important to realize that one specification could drive out several methods and several classes. It’s not necessary to pair subjects to classes, and specifications to methods. Notice the specification we implemented lead to the creation of 4 classes and several methods.
Once further specifications have been introduced at a more detailed level, it’s quite possible to switch contexts or subjects (or both) and further define specifications. However, this is not a license to start specifying technical terms in our native programming mindset.
Technical specifications should be reworded in less technical terms as much as possible. Nonetheless, it’s still possible to run into scenarios where technical speech is unavoidable such as in describing state machines.
Context Specification’s focus is not about providing a new tool to write tests in an innovative way. Programmers obsess over tools, and often the goal of Context Specification is eclipsed by programmer mentality to find new ways of doing things.
While it does provide a much different way of writing tests, it requires a change in traditional programmer thinking. A subject within a context must be thought of, and observable conditions form the specifications for that subject in its context.
This demands programmers sway their technical minds from more of a usability, high-level, business oriented mind while defining specifications, and back down to the essence of the technology they are using when writing the code that satisfies those specifications.
Providing documentation this way ensures that whomever can understand the subject and its context can understand the documentation and how that subject is used. This swaying prevents writing documentation that is too technical.
There are few instances when technical specifications need to be defined for a particular subject or context. However, one example where I’ve run into that particular situation involves a sub-system that performs connection caching. It is perfectly acceptable to write specifications for the caching operations under these circumstances because they define an inherent requirement.
Context Specification allows you to write documentation for subjects within specific contexts that are most important, and makes you more aware of what you are trying to test and write code for.
Domain awareness is strengthened by thinking more about a subject and context and less about object-oriented technical feats and the latest and greatest programming technology of the hour. The quality of a solution’s design and code is improved by getting closer to the true problems in the domain.
I hope you have fun writing specifications for your subjects and their contexts!
Special thanks to Scott Bellware for providing the inspiration of these article and for reviewing it.