DaedTech

Stories about Software

By

ChessTDD 34: Specflow for Pawn Movement

This episode featured a return to progress toward shippable features.  I refactored the first feature that I’d started to use the new, idiomatic Specflow approach.  This resulted in it making the most sense to have this be the feature for pawn movement and thus progress toward implementing the pawn’s movement as well as shaking out more bugs.

What I accomplish in this clip:

  • Refactored the old Specflow feature to look like the newer one.
  • Deleted a bunch of now-dead code and made the Specflow backing class a lot more concise.
  • Implemented HasMoved from the board perspective.
  • Fixed a bug in GetMovesFrom

Here are some lessons to take away:

  • I made a mistake in deleting dead code when I had a red test.  Part of the reason I got this wrong was that the IDE crashed and I sort of lost my place, but there’s a lesson here.  It’s easy to get distracted when you see dead/unused code (or something else similar) and go off on a tangent.  That’s fine, but be sure you’re green when you go off on tangents.
  • Thinking ahead about how they code you’re writing will be useful elsewhere is a double edged sword.  It’s good because it can lead to more efficiency and less future rework, but it’s also the first step along the path to gold-plating.  There’s no exact how-to I can offer for walking this line, but just being aware of it will help.
  • When things go wrong with acceptance tests, which are coarser-grained, integration tests, your next stop in figuring out the problem will generally be to move down the test pyramid and look for more details in your unit tests.  Unit tests are going to exercise the code in more granular fashion, so you should get good insights there.
  • I recommend favoring domain-specific, communicative exceptions coming out of your code rather than allowing boilerplate exceptions to be thrown to your callers.  If someone using your code gets an array index out of bounds exception or a null reference exception, they can’t be sure whether you screwed up in your code or whether they screwed up calling your code.  If you, instead, throw “BadBoardCoordinateException”, it’ll be very clear to callers of your method that you’ve anticipated what’s going on right now, and that they’re doing something wrong.
  • Deferred execution with Linq is really powerful and allows you to do some great things, but it also leads to subtle bugs.  I’ve written about this in the past, even.  Be careful and always remember to make sure you’re aware of whether or not you’re enumerating the sequence when you run into stuff like this.

By

ChessTDD 33: Scenario Housekeeping

Having fixed some bugs in the last few episodes, it would have been nice to make some progress with functionality, but there was housekeeping to be done first. I did some refactoring for board setup using the tables, compacting methods, and made the implementation of the moves checker correct. This will put me on much more sustainable ground as I go forward implementing game scenarios.

What I accomplish in this clip:

  • Fixed incorrect/incomplete implementation of checking for moves.
  • Refactored BuildBoardFromTable method.

Here are some lessons to take away:

  • When you make a test go red, don’t then take the opportunity to do a refactoring — even a small or inconsequential one.  Go back to green and then do it.  You want to be taking actions in batches as small as possible.  Doing 2 things at once is a recipe for confusing cause and effect.
  • I’m not sure how others feel about this, but I did something in this video that I do from time to time.  I had a green test covering an implementation that was too permissive — too easy to get right.  So I altered the test temporarily to green in a situation where it should have been red.  I then modified the production code to get the expected red, then reverted the test and verified that it was green.  This is the equivalent of writing another test, framed in the negative, and then taking that test from red to green.  I shortcutted that process because I didn’t want that other test to be left around when I was done.
  • I consider baking the names of types into class, method, and variable names to be a bad practice.  You might change the type they use and forget to update the name accordingly, and you’re also leaking implementation details.
  • A refactoring may well never seem perfect to you.  If you make sure it seems cleaner or better as compared to where it was, that’s progress.  Stick a pin it it and make a note to revisit later.  Not only is this good for avoiding diminishing returns on a given day’s effort, but it also removes you from the problem so that you can better assess readability later.

By

ChessTDD 32: Squashing a Subtle Bug

This was kind of a wild episode, inasmuch as recording oneself writing code gets wild.  This was the second bug driven out by the acceptance tests, but this one was subtle and buried fairly deep in the application logic.  This was another half hour episode as I employed a variety of techniques for hunting down and fixing a bug.

What I accomplish in this clip:

  • Fixed the bug discovered last time.
  • Left a few legitimate tests as breadcrumbs along the trail.

Here are some lessons to take away:

  • Use binary search kinds of techniques when solving problems.  For instance, if a two part boolean expression is causing a test to fail by returning the wrong result, comment out/delete one of the branches to see which one it is.  This helps you narrow the search without invoking the debugger or squinting at the code, scratching your head.
  • It’s important, when you have a failing acceptance test, to drill in and recreate the scenario in unit tests with less setup context around it.  This ensures that the problem is, in fact, a problem of the code under test rather than something going on with the acceptance test setup.  Getting this second failing test prevents you from chasing phantom bugs in your production code.
  • I try to use the debugger as little as possible when doing TDD, especially with the continuous testing tool.  But, for the occasion that you’d have to write a lot of assumption checking asserts, the debugger can be a handy way to see a number of different values that you want to check.  This was relevant when I wanted to see 4 different coordinate values.

By

ChessTDD 31: Look, We Caught a Bug!

A bit of time went by between when I recorded the code and when I narrated it, so pardon the unusual amount of rust there. But this episode was particularly interesting because an actual bug emerged and I fixed it. Yay for acceptance tests. After that, a second bug emerged, but I ran out of time. So there’s definitely a todo for episode 32.

What I accomplish in this clip

  • Got away from the C&P implementation of “then” for the new style of tests and implemented a usable one.
  • Discovered and fixed a bug in king’s moves.
  • Discovered another bug to fix next time.

Here are some lessons to take away:

  • When you’re stumped by behavior, particularly in integration tests, the continuous testing tool can help you run experiments very quickly.  Add a precondition assert to verify that your assumptions are correct.
  • TDD is not a catch-all against bugs, by any stretch.  I had a dumb bug in the implementation of the King class that I failed to catch, and everyone following along failed to catch (assuming someone would have reported it, anyway).  It wasn’t until I started simulating real production usage that these bugs started to be revealed.  Acceptance tests are critical.
  • The balance between ATDD and TDD is beneficial.  You’ll see going forward that when I find problems, I tend to use increasingly specific tests the way that you might be used to using step-through in the debugger.  Narrowing the scope of the problem with tests rather than the debugger has the advantage of leaving a trail of breadcrumbs that become guards against regressions as you go on.
  • This ATDD/TDD stuff works.  As you can see, I caught 2 bugs that could have escaped into production.
  • Never commit with red tests, obviously, but I also say never take a break with red tests (the way I would have to between clips).  If you have to go, comment or delete that red test, so that you can start fresh with green next time and reintroduce it.

By

ChessTDD 29: Finishing up the ASCII Board Builder

I actually recorded this episode right on the heels of the last one, so that I could keep a good rhythm with the ASCII builder class.  I finished that class up here.

Here’s what I accomplish in this clip:

  • Finished the class, as I mentioned.
  • Moved the class into the production code.

Here are some lessons to take away:

  • Sometimes you’ll write a test that goes red while writing the first line.  According to the strictest discipline, you should make that green and then keep going.  But if it’s a question of writing a few more characters or another line or something to get your fully realized red test into place, I, personally, think that’s okay.
  • If you’re going through your red-green-refactor cycle and, during the course of a refactoring, you introduce lines of code that none of the tests are hitting, be very careful.
  • If you’re in the middle of a refactoring and you start to wonder if maybe you aren’t changing the way the inputs and outputs could work, back out the refactoring work and get to a known, green state.
  • I’ve no doubt covered this before, but TDD is a great way to force yourself to think about how your API behaves with non-happy path inputs.  You’re writing tests that tease out implementation, rather than coding to get something working in a GUI.