Here goes, in outline format:
Test-Driven Development
I. Unit Testing
1. Able to run individual modules of working code in isolation to
explore their inputs/outputs and relationships to other modules:
a) from a command line interpreter
b) from an xUnit style test runner
c) from an IDE or object browser of some kind
2. Able to describe expected behavior of individual units of code
and verify them automatically:
a) using assertions to verify pre-/post-conditions and query results
b) using test doubles to hide complexity due to external
dependencies and verify interactions with collaborators
3. Understands the difference between various types of tests and
when/why to use each:
a) functional/acceptance tests which exercise the system end-to-end
b) integration tests which exercise multiple modules interacting together
c) unit tests which exercise a single behavior of a single
module in isolation
II. Refactoring
1. Able to perform small, behavior-preserving changes in a
disciplined manner:
a) using automated tools
b) manually, using the step-by-step procedures outlined in the
refactoring literature
c) combining several small automated or well understood
refactorings to effect a larger change
2. Able to quantify/verify the behavior of the code prior to and
after changing it so as to validate its correctness
III. Simple Design
1. Understands basic principles of design:
a) OO/Functional/Procedural design principles
b) Decomposition/Divide-and-Conquer, Information hiding
c) Clean Code
2. Able to recognize indications of good/bad design in working code
3. Able to restructure code opportunistically to remove code smells
and to bolster/enhance the design
4. Able to Code by Intention, designing an API by imagining how it
will be used
IV. The TDD Cycle
1. Able to create a simple test which compels a vanishingly small
bit of implementation in order to pass
2. Has sufficient mastery of the language to create a small, simple
implementation
3. Able to apply Simple Design and Refactoring to simplify and
improve the design without breaking the test
4. Able to repeat the Red-Green-Refactor cycle quickly in small,
rapid increments
This is something people new to unit testing or TDD struggle with -
the "what test do I write next?" problem. It's the ability to see
several moves in advance, to know where you're going and drive in that
direction by writing the correct tests, to see which tests are still
missing, to get across the river one stroke at a time. All while
listening to what the code and tests are trying to tell you about your
design.
I like your module I, Unit Testing, because it represents the raw
technical skill to do this sort of work independent of intention or
direction. II.1.c (" combining several small automated or well
understood refactorings to effect a larger change") gets at what I'm
looking for, but in a refactoring context and not a TDD one.
I suggest adding a #5 to The TDD Cycle:
5. Able to combine and direct small, rapid increments to evolve the
design and implement larger features, attending to code smells and
design improvement opportunities as they appear
(It could use some wordsmithing, I'm sure.)
- John Stoneham
Good point. I think I'd put that under the Simple Design heading.
Maybe something like:
5. Able to build strategically towards a complete solution while
managing complexity
a) understanding the difference between essential and accidental complexity
b) recognizing and applying software design patterns
c) using refactoring to change strategies on the fly
> This is something people new to unit testing or TDD struggle with -
> the "what test do I write next?" problem. It's the ability to see
> several moves in advance, to know where you're going and drive in that
> direction by writing the correct tests, to see which tests are still
> missing, to get across the river one stroke at a time. All while
> listening to what the code and tests are trying to tell you about your
> design.
>
Perhaps I need to add something to the Unit Testing section:
4. Able to select test cases for maximum coverage/effect
a) tests that reveal the intention of the code by exercising the
API as it is meant to be used
b) tests that cover boundary conditions and other areas of complexity
> I like your module I, Unit Testing, because it represents the raw
> technical skill to do this sort of work independent of intention or
> direction. II.1.c (" combining several small automated or well
> understood refactorings to effect a larger change") gets at what I'm
> looking for, but in a refactoring context and not a TDD one.
>
> I suggest adding a #5 to The TDD Cycle:
>
> 5. Able to combine and direct small, rapid increments to evolve the
> design and implement larger features, attending to code smells and
> design improvement opportunities as they appear
>
I really meant for that to be covered by #3 (The "refactor" in
red-green-refactor.) Did my addition to Simple Design account for what
was missing, or do you think I need something more?
I like this content, but I'm having trouble placing this under a
Simple Design heading. What about Evolutionary Design instead?
> 4. Able to select test cases for maximum coverage/effect
> a) tests that reveal the intention of the code by exercising the API as it is meant to be used
> b) tests that cover boundary conditions and other areas of complexity
I like this.
> I really meant for that to be covered by #3 (The "refactor" in
> red-green-refactor.) Did my addition to Simple Design account for what
> was missing, or do you think I need something more?
I think it ought to be touched on in both sections in some way - both
refactoring and TDD are activities that are performed with a direction
in mind via small steps. In TDD, I think we ought to capture the
gradual accumulation of functionality that becomes larger features.
How about something simpler than my earlier suggestion:
5. Able to build up small, rapid changes to form larger features while
maintaining a simple design.
(but I -really- wanted to get to use the word 'agglutinate'...)
- John Stoneham
Hi Adam,
I use the Dreyfus Model to coach all kinds of skills and to provide a
roadmap for people to improve. These are the models I use for TDD and
Acceptance Testing, from a developer-centric view. In my model the
"Competent" level allows an individual to be independently successful
with that skill, without relying on help from anyone else, though they
may still experience some pain.
I'm sharing them with you because I find that the idea of a journey
when picking up skills is powerful, and in case you find something
useful in my BDD-centric language. It's not quite complete - you've
got some ideas I'd like to add to this model, if you don't mind.
TDD
Novice
Writes tests before code, most of the time
Beginner
Uses tests in combination with IDE to drive code
Tests fail before code is written
Adds tests to legacy code
Competent
Gives meaningful names to test methods
Tests provide documentation for the classes
Uses mocks or other test doubles to decouple dependencies
Knowledgeable
Refactors tests
Uses tests to describe why the code is valuable
Uses tests as examples of how to use the code effectively
Drives out responsibilities and elegant design using TDD
Ensures that tests are independent and robust
Expert
Uses TDD to derive role-based interfaces
Drives code from outside-in
Understands that TDD isn't just about testing
Understands the value of testing separately to TDD.
Acceptance Testing
NB: A UI - user interface - is the point at which the code provides
value to its customer. Its customer may be a third-party application,
another system, etc. - it doesn't have to be an actual GUI.
Novice
Writes acceptance tests
Beginner
Writes acceptance tests before writing the code
Competent
Ensures acceptance tests are legible and maintainable
Talks to QAs about scenarios before starting development
Uses acceptance tests to investigate bugs
Knowledgeable
Does not use production code when writing tests
Tests from the UI
Writes simulators for 3rd-party applications
Uses scenarios in conversation to establish a shared
understanding of the domain
Uses acceptance tests to reach a shared understanding with QAs
and the business of what 'done' looks like
Expert
Merges automated tests to minimise set-up costs, using one as
the context of another to form regression tests
Can establish different environments for acceptance testing
and ensure that tests work in all
Can manage set-up of complex scenarios pragmatically, using
the UI or data-driven contexts as appropriate.
Cheers,
Liz.
--
Elizabeth Keogh
l...@lunivore.com
http://lizkeogh.com
http://lunivore.com
I don't know much about the Dreyfus Model. It came up at the workshop
and I read the wikipedia article about it.
I think your list is great. I can definitely see how a person could
learn TDD by working through those steps with some gentle guidance.
You are certainly welcome to use things from my list to modify yours.
Possibly, we could also use your list as a "course" to measure against
the inventory.
In the other threads (Not sure if you've been reading them all) I
noticed a pattern:
A: Here is skill X including "steps to mastery."
B: I don't think those are the right steps. I would add foo. I don't
think we need to include bar.
C: I think we need bar. I don't do foo.
D: Perhaps there is more than one correct way to do it. Perhaps we
shouldn't even describe a single way to do it.
I have been saying that I think we have to say *something* about how
to do it, otherwise we have no idea what people will do.
Charlie Poole has been saying that he thinks some of the skills we are
talking about (And TDD is one of them) are too course grained. That in
fact TDD must constitute numerous smaller skills which could be
individually described.
That was the idea behind my list. Could I come up with a sort of story
map of independently valuable skills that together constitute TDD? If
I could, could we use it to inform the types of skills and level of
detail that the Skills Inventory should contain? Could we then do the
same thing for other high level skills?
> --
> ________________________________________________________________
>
> Much of the discussion in the group is predicated on several resources summarized at http://sites.google.com/site/agileskillsprojectwiki/home Please review this regularly. To get editing permissions for the wiki, send a note to both d.andre...@gmail.com and redho...@gmail.com .
>
> ________________________________________________________________
> You received this message because you are subscribed to
> the "Agile Developer Skills" group.
>
> To unsubscribe from this group, send email to
> agile-developer-s...@googlegroups.com
>
> For more options, visit this group at
> http://groups.google.com/group/agile-developer-skills?hl=en?hl=en
>
- How to write testable code. As Miško Hevery says in one of the links
below, "Well there are no tricks to writing tests, there are only
tricks to writing testable code. If I gave you testable code you would
have no problems writing a test for it."
Writing testable code can further be decomposed into things like using
dependency injection, avoiding global mutable state, writing small
focused classes (Single Responsibility Principle, Open Closed
Principle etc.) and so on.
I think that test-first forces the developer to write testable code,
and doing test-after is hard because you don't yet know how to write
testable code. If you have experience in TDD, then you could write
testable code even if you would sometimes work test-after.
Links for learning more:
http://googletesting.blogspot.com/2008/08/by-miko-hevery-so-you-decided-to.html
http://googletesting.blogspot.com/2008/11/guide-to-writing-testable-code.html
http://googletesting.blogspot.com/2008/11/clean-code-talks-dependency-injection.html
http://googletesting.blogspot.com/2008/11/clean-code-talks-global-state-and.html
- Using test doubles. A TDD practitioner should know when to use test
doubles and what kinds of test doubles fit a situation the best.
He should also have experience about the mock object frameworks that
are available for his platform, and should know the difference between
mock frameworks (JMock, EasyMock) and spy frameworks (Mockito).
Some links:
http://code.google.com/testing/TotT-2008-06-12.pdf
http://xunitpatterns.com/Mocks,%20Fakes,%20Stubs%20and%20Dummies.html
http://martinfowler.com/articles/mocksArentStubs.html
http://hamletdarcy.blogspot.com/2007/10/mocks-and-stubs-arent-spies.html
- Writing self-documenting tests which specify what is the desired
behaviour of the system (see Behaviour Driven Development).
When a test fails, there are three options: (1) the code is broken and
needs to be fixed, (2) the test is broken, but still needed, and it
needs to be fixed, (3) the test is not anymore needed and needs to be
removed. To be able to know what to do in each situation, the tests
should have long descriptive names, which explain the intent behind
the test. Each test should tell that what is the behaviour being
specified by the test.
The style that I have found to work for me is to write "tests as
specification" (for examples, see http://github.com/orfjackal/tdd-tetris-tutorial
- the 'beyond' branch contains the most complete code). I have also
seen many people using a style which I call "tests as examples", for
example as seen in Uncle Bob's Bowling Game Kata, which IMO does not
as well document the intent behind each test (but in some situations
is easier to write and less verbose).
Some links:
http://dannorth.net/introducing-bdd
http://techblog.daveastels.com/2005/07/05/a-new-look-at-test-driven-development/
http://techblog.daveastels.com/files/BDD_Intro.pdf
- It would also be useful to document some of the common stumblings
that learners of TDD face. Here are some things that I've noticed
(from myself and my students) to be hard to do when you're new to TDD:
* Being disciplined enough to always write a failing test first. The
temptation of returning to the old ways is high.
* Proceeding in small steps. Each new test should specify only a
little bit new behaviour, and passing a new test should not take a
long time. A balance is needed between writing small enough steps
(otherwise making the test pass will be hard) and not writing too
small steps (otherwise the progress will be slow).
* Knowing which test to write next. BDD's "what is the next most
important behaviour" question can help. Also Kent Beck's
Simplification strategy (http://www.infoq.com/presentations/responsive-
design) is useful when writing the first tests for a new class.
* Writing tests which serve as good documentation. Choosing
descriptive names for the tests.
* Writing tests which are decoupled from the implementation details.
If you need to change the tests when refactoring, it might be a code
smell that the tests are too tightly coupled to some implementation
details. It's better to write tests, which are focused around the
desired behaviour, and not the implementation. For example if you have
a test "testSetFoo" and then you remove/rename the "setFoo" method as
part of a refactoring (for example change it to be a constructor
parameter), then the test also needs to be changed, even though the
behaviour provided by the system stays the same.