On 10 Dec 2008, at 16:04, James B. wrote:
Nonetheless, in the step definitions I must test the behaviour in a
fashion which first drives and then confirms the implementation
details.
This is not the path to BDD, it’s the path to state-based testing.
Matt has already explained this well:
“How you chose to satisfy the user’s need for that behaviour is an
entirely separate matter. A large benefit of having Acceptance Tests,
IMO, is that you could radically change your implementation under the
hood, such as switching to a different authentication mechanism, and
the features would still be valid.”
I’m guessing the customer for this project didn’t ask for AuthLogic,
they asked for part of the app to be protected. You should only write
acceptance tests for things these users care about.
If the chosen implementation depends upon a third party plugin or gem
then surely one should test for its provision? What is the
alternative?
Write tests to test that gems behaviour? Does that not violate TDD/
BDD
standard of only testing your own code?
Don’t confuse library code with code you write to use this library.
The AuthLogic config in your app is code too, and it adds behaviours
to your app. The fact there are 10, 100 or 10,000 lines of code
behind it is irrelevant.
There’s two attitudes you can take in spec files: write specs that
your code calls some library, or write specs that describe the
behaviour that library provides.
For example (the following is rushed, contrived code but illustrates
the idea):
class Sorter
def initialize(array)
@array = array
end
def sort
array.sort
end
end
now when you spec
@array = [5,9,6,1,4]
@sorter = Sorter.new(@array)
are you going to write
@array.should_receive(:sort)
or
@sorter.sort.should == [1,4,5,6,9]
?
I believe that in this case, the second option is more valuble: you
can change the implementation to sort other collections. In the first
case, you could refactor the code without breaking the existing spec.
This is important: The ability to refactor code without breaking the
specs is one of the major sources of value in the specs. This lets
you reduce technical debt with no risk to your app. If you rely on
the implementation in your spec, you have to change the spec, and
therefore you have no guarantee that the behaviour your users asked
for still works.
The definition of refactoring is changing the implementation of code
without changing its behaviour (and by extension its specs). Without
this constant your code will be fragile.
As a extreme, albeit small, example- a while back I took the code for
a presentation I gave[1] and changed the app framework from Ramaze to
Merb (it only took about 10 mins). Because the specs all ran across
the public HTML interface, there was no visible difference. If the
feature steps relied on the implementation this wouldn’t have been
possible.
I use Celerity[2] instead of Webrat and write all steps across an
app’s public interface for this reason.
The imaginary end user of any non-trivial application is actually an
assemblage of disparate roles, many of which consciously do not
overlap.
Some of those users are the system administrators which tend to the
day
to day background maintenance tasks, some of which are automated and
some of which are not. Some of the application users are, in truth,
the
implementors themselves.
This is fine, but you should still write acceptance tests from the
perspective of the user. If the user is a shell script, drive your
app on the command line. If the user is a human, drive it over the
visible interface. But do you really have any users that access your
app code directly?
Hope this gives another perspective on the subject.
Ashley
[1]
http://aviewfromafar.net/2008/10/2/geekup-sheffield-vi-from-specification-to-success
[2] http://celerity.rubyforge.org/
–
http://www.patchspace.co.uk/