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

Chess TDD 30: Starting To Be Idiomatic With SpecFlow

This episode went so poorly, I thought briefly about scrapping it and starting from scratch… but that would not be true to the premise I established at the outset where I’d do this unedited, flubs and all.  Having finished with the AsciiBoardBuilder, it was time to start putting Darren Cauthon’s ideas into play.  You can read up on that here.  I sized up what he had done and, in spite of knowing very little about SpecFlow, decided that I only needed certain parts of it for my purposes.  This turned out to be a mistake as something that I thought he had just added for illustrative/cosmetic purposes was decidedly non-cosmetic, and it took me a lot of floundering to figure that out.  Now, that’s not uncommon for me, per se — I’m a “figure it out by breaking it” sort of person, but it’s not exactly the stuff of scintillating videos.

Here’s what I accomplish in this clip:

  • Got the first idiomatic SpecFlow test written in the new feature.  Barely.  And ugly.

Here are some lessons to take away:

  • When using someone else’s example as a template for learning something you don’t yet know, don’t jump the gun and start tweaking and changing things before you get the example working.  Do as I say, not as I do.
  • No matter how long you’ve been doing this, you’ll still make off by one errors and get array bounds arithmetic wrong when it’s complicated.  Improve the odds in your favor by using TDD or, by some other mechanism that you come up with, if applicable.
  • When you find yourself writing a good bit of logic in test code (meaning, you’re writing a lot of code that you aren’t test-driving), ask yourself whether you could move the logic to production or find some other way to tease it out with TDD.  You can see by my floundering here that you become decidedly less productive when you’re writing a lot of code and just hoping for the best.
  • Using NCrunch, it’s pretty easy to run quick experiments to help with my debugging.  One such example was to start hard-coding the row/column indices to see for which ones exceptions were actually generated.
  • Similarly, putting a temporary precondition assert at the top of a test method to check your assumptions can also be a big help.  This is what started me down the path of realizing my mistake with the table’s header.  I finally sanity-checked my assumption that the table had 8 rows to find that it really only had 7.