CodeBetter.Com
CodeBetter.Com
RSS 2.0 via Feedburner
           Do you Twitter? Follow us @CodeBetter

Jeremy D. Miller -- The Shade Tree Developer

Under the hood and working with .Net, TDD, Software Design, and Agile Stuff

August 2006 - Posts

  • An idle thought

    I've had a couple conversations recently about Eric Evan's Domain Driven Design book.  I like the book, I like quite a bit of the DDD approach, I think it's an important book, and I'd recommend giving it a read.  That being said, we came to the conclusion at AgileATX lunch today that the DDD book might have supplanted the "Gang of Four" book as the leading cause of overdesign (except for SOA of course).  

  • Can we redefine Unit Test?

    It's just an opinion, but I'm thinking that the hardcore Feathers definition of a "Unit Test" needs to be restated, or at least not applied so literally.  I used to get a little annoyed with one of my former colleagues who was a bit anal in his definition of a unit test.  "Oh no, that test uses two classes, it's an integration test so we should put it over here."  Let's worry more about the goal of writing a test and less about the mechanics of a test.

    I typically like to set up one assembly for unit tests and another for integration tests.  The point of the separation isn't really unit vs integrated so much as it's fast test versus slow test.  I don't really care that much if more than one concrete class is involved in a single "unit" test (with some provisos listed down below). 

    The important thing about TDD/BDD is to use a test to specify the desired behavior of a piece of code.  I see people hurting themselves by putting too much emphasis on the letter of the "unit test" law and not enough thought into the spirit.  The goal isn't to have a disconnected "unit test" for each piece of code, it's to have a test that specifies the behavior of a piece of code.  Thinking of some specific examples, I hate to see people try to unit test data access code by mocking ADO.Net.  Another example that bugs me is seeing people obsess with trying to mock the file system.  By all means, do put a thin wrapper around that stuff to isolate the data access from the rest of the code, but drive the construction of code that only interacts with the database by interacting with the database.  If a class's only raison d'etre is to do data access, test it against the database!  Writing a "unit test" with mocked out ADO.Net calls doesn't add much value and sucks down time.



    Here's my alternative version of the Feathers list:

    A Unit Test is:

    • Fast
    • Short
    • Easy to setup, control, and verify, i.e. very few variables in the test equation
    • Specifies the behavior of a minute fraction of the code
    • Tells me exactly what is wrong.  "This, this code right here is wrong"  If a test just tells you that something is wrong, it's not a unit test, it's an integration test.  In other words, if I need a debugger to fix a broken test, it's most likely an integration test.

    Having a little cluster of two or more concrete classes in a unit test doesn't bother me, as long as the unit test meets these requirements.  The real "categorization" of tests for builds and developer runs is strictly on a Fast vs. Slow split.  By that definition, almost any test that calls outside a single AppDomain is a slow test.  Any test that requires some kind of environment setup, or involves an intensive calculation, gets shoved into the "slow" integration test assembly.

    In a more general note, any automated test that is hard to diagnose when it fails, isn't very valuable.
     

  • Raganwald

    .Net is going to pay the bills for the forseeable future, but I've been spending quite a bit of time the last couple months learning about other platforms and approaches.  I love Raganwald's blog because he's always talking about interesting subjects that I don't know much about like dynamic languages, functional programming, and metaprogramming.  His latest post is about whether or not Extreme Programming was possible without Smalltalk.  Go check it out. 
  • Funny quote

    My partner when asked by Philip Marlowe* if the items on a punchlist marked as "need input from Philip Marlowe" were complete:  "the items that need more input are not complete because they need more input first."

    * The name is changed, just in case you're not a fan of the Big Sleep
  • A Rant about the Software Lifecycle

    I'm not going to waste your time on another iterative versus waterfall argument.  What I would like to say is that if you're going to go iterative, everyone -- developers, testers, business analysts, project managers -- needs to be working iteratively.  Some of the advantages to an iterative process are due to attaining a synergy between the software disciplines.  Ideally, the testers and analysts are working on the exact same set of features as the developers.  I find it much easier to establish that ubitiquous language concept the Domain Driven Development folks talk about between developers, analysts, and testers if we're all working on the same thing.  It's easier for each group to ask questions of the other groups because the subject is fresh on everybody's mind.

    For a variety of reasons our tester is testing some features that we coded about two months ago.  I built an exhaustive (for a developer testing his own code) suite of Fitnesse tests for all of the functionality, but without much input or involvement from the tester.  Flash forward two months.  We still can't technically claim the work as "done, done, done" because the tester is just now looking at it.  Much worse to me is the fact that the tester isn't familiar with the Fitnesse fixtures or the system.  He has not found a single bug in the backend pieces, but he's getting slowed down by the time it's taking to learn about the way the Fitnesse infrastructure is working.  I'm struggling to answer some of his questions because of the length of time since I worked on these features.  If we'd been able to do this together, we would have been on the same page and eliminated some inefficiency between us.  I would have built the Fitnesse fixtures to *his* specification, cutting down the amount of time it's taking him to test the code.

    Working a sustainable pace is usually cast as not working lots of overtime, and that's certainly a big deal.  What's not discussed as often is the fact that the minimal overtime is purchased at a price of no down time.  The development team and the testers have to be fed with detailed requirements for each iteration -- starting almost immediately with iteration #1.  When you're working iteratively you start coding and testing earlier.  Whoever is responsible for eliciting the requirements needs to be communicating with the developers and testers all the time to make sure that the next set of detailed requirements are ready for each iteration.

    I've tried in the past, and seen many other teams, to do design and coding iteratively while the rest of the team works in a serial waterfall style.  It's not pretty and you lose a lot of the advantages of just-in-time requirements, continuous testing, and adaptive planning.  You're just not coordinated with the rest of the team and vice versa.

    While I'm ranting away, let me try to knock down a pair of common misconceptions about software lifecycles that bug me:
    1. Spiral (or Iterative only) and Iterative & Incremental development are two very distinct lifecycles.  An iterative process means that you take a subset of the whole and work that subset to production quality code that could be deployed before starting the next subset.  A spiral process is developing the whole system in a series of prototypes with increasing quality until the whole is ready to ship.  There are people who argue which is more effective, but they are distinctly different (my opinion is that Spiral is a bad idea because people just rush the early prototypes into production).  Here's a pro-prototyping, anti-Emergent Design link (points for the photo of the 360 bridge in Austin on the page).  I disagree with basically everything ever written on Software Reality, but in the interest of balanced coverage...
    2. Agile versus Waterfall is a false dichotomy.  Every Agile process is *an* iterative & incremental process, but not every iterative process is Agile.  A big portion of the folks I see actually try to defend waterfall development are really just trying to take an anti-Agile stand because they're irritated at the hype.  RUP is supposed to be iterative.  ICONIX is iterative.  MSF is supposedly iterative.  None are Agile processes.  You *can* work iteratively in a formal process.
    Lastly, an iterative cycle doesn't mean that you build a different application layer per iteration.  That's BDUF all the way.  If you're working iteratively you should be building completely functional features, so you're going to have to work vertically, not horizontally.  Building a layer at a time is a great way to fail dramatically or work inefficiently by creating speculative code.
  • I was first dadgummit!

    I get an email like this or a query fairly regularly:
    I was wondering why you decided to write StrucutreMap rather than utilizing one of the existing .NET IoC containers (Spring.NET, Castle etc.).

    StructureMap was first.  The port of Spring for .Net was announced right after I released StructureMap in June 2004, and I'm not sure when Castle was released but it was later.

    Arguably StructureMap was the first production ready IoC/DI tool for .Net, but I'm not sure when, and if, PicoContainer.Net was ready for real usage.  At the time I released StructureMap I got a little grief from the Pico guys for building a new one instead of using Pico.Net.

    Moral of the story?  If you want you're open source tool to succeed, do a better job getting publicity and a community than I did with StructureMap.


  • Because You Told Me To Drill Sergeant!

    We did a rewrite of a truly wacky application component last year that supplied a steady stream of head scratchers.  Today I walked back into the room from a late lunch and overheard a customer support manager getting a little hot under the collar over some unexpected, strange behavior in the system.  My colleague was explaining what the system was doing when the support manager blurted out "why is it doing that?"

    All of us in unison shouted at the speaker phone "because that's what [old system] did!"

    Oddly enough she didn't care for that answer, but we were able to turn that rule off easily enough in the configuration.

  • Creating a Maintainable Software Ecosystem

    The blogosphere is full of discussions and arguments on the best way to write and design software.  It might be worth the effort to stop and go back to first causes -- just what quality or qualities do we want in our code?  What are we trying to achieve?  For me as a developer on enterprise software systems, that answer is easy.  As far as I'm concerned, maintainability is the single most important quality of code.  You might be tempted to say productivity, but since most of our time is spent modifying or extending existing code, that productivity is predicated upon the maintainability of the code. Productivity over any extended duration, even within the initial project, can only be ensured by creating a maintainable codebase. 

    Earlier this year Jim Shore launched a treatise called "Quality with a Name" laying out a powerful definition of good design.  Quoting his conclusion (emphasis mine):

    A good software design minimizes the time required to create, modify, and maintain the software while achieving acceptable run-time performance.

    Enterprise software systems change.  Business rules change, technology platforms change, third party dependencies are upgraded.  Again from Jim Shore, "...most software spends far more time in maintenance than in initial development."  Enterprise systems typically aren't replaced because they stop working.  The end of life cycle for an application or system is often brought about because the system has become too difficult, risky, or expensive to modify to keep up with evolving needs.

    Maintainability has become a near obsession for me because I've spent much of the last two years modifying, extending, or flat out rewriting legacy code.  Inevitably, much of the code has proved to be difficult to to work with.  Our efficiency has been hampered on multiple occasions because of bad existing code and poor or nonexistent development infrastructure.  We've been noticeably faster when we're working with the newer code written with TDD, NAnt builds, and FitNesse tests.  Maybe more revealing, we've become much more efficient with our legacy code when we retrofitted a lot of test and build automation around the code.

    Finally, one last quote from Mr. Shore:

    “…the goodness of a design is inversely proportional to the cost of change.”

    I agree, and this "manifesto" was originally going to be completely about coding and designing software for maintainability.  I still think that the quality of the code is the single biggest factor in the longevity of an application, but in reflection, some of the biggest gains my team has made with our legacy systems has been the creation of more comprehensive build automation, faster builds, better configuration management, and a body of automated tests.  It's not just the code, it's the entire ecosystem.  Personally I think that continuous, adaptive design is the most reliable mechanism to arriving at a good design, but then again, continuous design is most easily accomplished when there is a high degree of feedback for any changes like automated builds and tests.  So for the moment being, here is my vision for the practices and infrastructure you need around the code itself to create a maintainable software ecosystem.

    Answer these Questions with a Yes

    If you want to create a maintainable code environment, you'll need to be able to answer all of these questions in the affirmative.

    1. Can I find the code related to the problem or the requested change?
    2. Can I understand the code?
    3. Is it easy to change the code?
    4. Can I quickly verify the changes?  Preferably in isolation.
    5. Can I make the change with a low risk of breaking existing features?
    6. If I do break something, is it easy to detect and diagnose the problem?

    Unsurprisingly, my answers to these questions largely come from Agile/XP practices -- Test Driven Development, Refactoring, Continuous Integration and Acceptance Testing.  To answer yes to all six questions, I say you need very solid, clean, well-structured code and multiple layers of effective feedback to let you know when things are wrong and what exactly is wrong to quickly correct problems.  Agile development is nothing but a set of practices to maximize feedback.  Maintaining a high quality of code draws on much older practices and values, but I think that Test Driven Development and designing for testability are the single most effective mechanism to enforce some of the traditional definitions of good code structure that enable easier modification of code:  separation of concerns, high cohesion, and low coupling.

    Journey to a Maintainable Software Ecosystem

    As a developer extending an existing system you're often the protagonist in the the old Myst computer game.  In Myst you're a traveler exploring a strange world where all of the people have disappeared and you solve a series of obscure puzzles to continue along your journey.  Now, let's take a tour through my vision of a maintainable software ecosystem.  Say you're a developer tasked with making some extensions to an existing codebase, and the original developers have all disappeared.  Unlike Myst, in this healthy ecosystem there should be a sign or signs at every point to say "go here next" or "you have a problem right here." 

    I start by finding a brief document or Wiki page that tells me what software has to be installed for the code (IIS, Sql Server/Oracle, etc.) and the all important location of the source control repository.  When I retrieve the root of the source repository I also get a copy of everything else that the code needs to execute and the master build script.  I then run that build script that compiles the code, sets up all of the necessary environment configuration, and runs a set of unit and integration tests.  As soon as I see this build finish successfully, I'm reasonably assured that my box is able to execute the code.  Once I have the code opened on my box, I can see that the code is well factored and largely orthogonal in structure.  I'm able to find the place where my new code should go and the patterns that the existing code follows so I can maintain some consistency.  Because the code is loosely coupled, I can add my new code and easily unit test it in isolation without having to deal with much of the existing code.  Once the new code is complete I run the build script again, and assuming the build script succeeds, I check the new code back into source control.  A continuous integration server detects the changes, builds the latest code on the clean build environment, runs a more exhaustive suite of automated tests, and finally creates a deployment package that can be used to push the code to a testing environment.  I can confidently push the code quickly into production because there is a near comprehensive suite of automated regression tests that largely reduce the cost and risk of regression testing.  The risk of propagating code is minimized by a self-diagnostic deployment strategy that can tell you what and when something is deployed incorrectly.

    The previous paragraph doesn't have to be just an imaginary place.  It's obviously easier to accomplish with "Greenfield" code, but it might be more important to get that existing, strategically important codebase to that state.

    Invest in Continuous Integration Infrastructure

    Of all the practices in Agile development, the one practice that I would recommend without reservation is Continuous Integration.  If you're a team brand new to Agile or XP development and looking for a place to get started, I say start with CI (with TDD an immediate second).  I recently read an article that aptly describes Continuous Integration as having a continual conversation with the system.  If you're not already familiar with CI, it's the practice of running a full integration build on the most current version, usually including environment setup and unit tests, on every single check-in to the source control repository.  The Continuous Integration infrastructure is most effective when it's coupled with a developer attitude of frequent check-ins.  A lot of teams approach an automated build script as overhead, a nice thing to do, but one that can be skipped in a time crunch.  Not so.  A comprehensive automated build script is strategic to maintaining productivity over the lifecycle of a system.  It's an investment, not a cost. 

    A good CI infrastructure and process can reduce friction in working with a codebase by:

    • Faster feedback from any changes made to the system
    • Providing better transparency into the changes happening to the system
    • Propagating environmental changes and code changes more rapidly while maintaining control
    • Ease integration issues by dealing with them earlier in smaller chunks

    A vexing problem in maintaining an existing codebase is not being able to exercise the code in your immediate development environment.  After all, how can you really know that the code works if you can't run it?  Enterprise applications almost always have dependencies on external libraries and specific server configuration.  Too many times I've seen developers stopped in their tracks because of issues with their development environment.  In the past, I've spent up to a couple of weeks just trying to get an existing codebase to function on my workstation before I could begin writing new code.  That time is inefficiency, and a preventable waste of time.  Even with a build script I've seen developers spend days trying to work out the kinks in their system to make the build script function.

    The best answer in my playbook is a completely comprehensive automated build script backed up with a modicum of documentation.  Obviously, you're not going to install Sql Server or Apache from the build script.  Other than big ticket items, the build script should completely lay down all of the environmental dependencies.  Our build script will build a local copy of the database, setup virtual directories on IIS, register COM (must die) dependencies, installs the windows services, and make all of the relevant registry entries necessary to execute the code.  Theoretically, we could bring a brand new developer in and get the entire suite of applications running on the new workstation in a couple hours (if we were ever allowed to hire again anyway).  Even with an unchanging team that's important because the application itself is always changing.  Every new project we build adds new environmental changes to the system.  Keeping all of the setup in the automated builds helps to get these changes propagated to the other developer workstations. 

    Here's a scenario that's unfortunately common in the .Net world (I have no .  A developer or a pair builds a new feature that depends on a third party library.  The third party library comes with an MSI that puts the assemblies in the GAC.  The developers continue on with the development on their workstations and everything works perfectly -- until that code is moved to a different box.  If the team uses a Continuous Integration [link] strategy, that problem is going to be spotted immediately.  If the team is being diligent about their Continuous Integration strategy they'll automatically add the new environment setup to the source control tree and build scripts.  Even if they don't, the CI build is going to give them immediate feedback that there is environment setup missing

    One of the most pernicious velocity killers is friction or uncertainty with migrating code between development to testing and production servers.  I've seen shops try to beat the issue with lots of ceremony and paperwork, but it's an inefficient and ineffective mechanism by itself.  I watched one team take up to a week to setup a testing environment for a particular branch of the code -- and they had to do this a dozen times a year.  We've beat the issue by automating everything that moves.  We've extended our Continuous Integration infrastructure to include moving successful builds to our testing servers on demand.  We built environment testing into our code to quickly troubleshoot and detect problems with an installation of the code.  We have not had a single problem with a testing deployment since.  Speeding up the feedback cycle between development and testing has certainly helped us, but the improved reliability and control over the testing migrations has made a tremendous difference.  The end result for us is the ability to quickly shift code from development to testing, know that the installation is valid, and all the while have accurate traceability from the build products being tested to the exact version of the source code.  It's a great balance of speed versus control with relatively little developer overhead once you're past the initial creation of the build infrastructure.

    Take a look at the Capistrano project from the Ruby world.  Envision a world where you can reliably do production code pushes and rollbacks with a single mouse click.  If you had that ability, and some shops already do, how much faster could you deliver new features and fixes? How much more incrementally and iteratively could you work (think Google)?  If a production push is scary, or takes an act of congress to move through your process, your system isn't going to be that easy to maintain -- even if the code is pristine. Someday I'd like to have the one click production push.

    Getting the Source Code Under Control

    You have to be able to find the right code, and preferably without spelunking through an ancient VSS repository.  There needs to be a single, authoritative source for the code and its dependencies.

    It should go without saying that using source control software is nearly mandatory in any professional software endeavor.  My colleague and I have between us given four presentations on Continuous Integration in the last 18 months.  After every single presentation somebody approached us with a horror story of a multi-developer team working today without any source control.  That's borderline insane, but just using source control may not be enough.  One of our mission critical subsystems has its source code scattered across a couple different repositories, none of which can be said to be the authoritative master repository.  It's a major source of concern.  A sadly common anti-pattern in software development is creating build products directly on a developer workstation and migrating those products.  At this point it doesn't matter if the compiled build products themselves are checked into source control because they aren't traceable.  We did this routinely at one of my previous jobs.  Two years after I left I got a call from a friend of mine who had checked out a VB6 project I'd written to fix a production issue. It wouldn't compile because a class file was missing. Oops.

    Again going back to first causes, what I want to achieve in my software ecosystem is the ability to accurately trace at all times the build products installed on testing, development, and production environments to the exact versions of the code.  It's awfully hard to diagnose problems in production if you're not sure which version of the code you should be looking at. In the example above we had a formal process recording production and testing migrations, but no real traceability from the installed binaries to the exact source code.  Good traceability doesn't have to be difficult.  For us, traceability is almost a byproduct of using Continuous Integration.  The first step is to simply get all of the code and anything that's necessary for the code to function into the source control repository.  Continuous Integration is only effective if there is a single, authoritative repository for the source. Part of our CI build is embedding the CruiseControl.Net build number into all of the .Net assemblies.  It's just a NAnt task that looks like this from my StructureMap build file:

            <asminfo output="Source/CommonAssemblyInfo.cs" language="CSharp">

                <imports>

                    <import name="System" />

                    <import name="System.Reflection" />

                    <import name="System.Runtime.InteropServices" />

                </imports>

                <attributes>

                    <attribute type="ComVisibleAttribute" value="false" />

                    <attribute type="AssemblyVersionAttribute" value="${assembly-version}" />

                    <attribute type="AssemblyCopyrightAttribute" value="Copyright (c) 2005, Jeremy D. Miller" />

                    <attribute type="AssemblyProductAttribute" value="StructureMap" />       

                    <attribute type="AssemblyCompanyAttribute" value="" />

                    <attribute type="AssemblyConfigurationAttribute" value="${project.config}" />

                    <attribute type="AssemblyInformationalVersionAttribute" value="${assembly-version}" />

                </attributes>

                <references>

                    <include name="System.dll" />

                </references>

            </asminfo>

    At the end of each successful CI build (compile, unit tests, integration tests), CruiseControl.Net creates tags the source control repository with the CruiseControl.Net build version.  Only successful, versioned CruiseControl.Net builds are ever migrated to testing or production.  The key point is that we can pull a version number off of one of the assemblies on the test server and immediately find the exact version of the code from the source control repository. 

    Don't forget about the database, either.  Database changes have a bad tendency to fall through the cracks.  Treat the database schema as just part of the code.  It's a stupid, unnecessary risk to put database code through a completely different change management process than the code.  It almost guarantees that the code will not be synchronized with the version of the database schema.  The idea of a DBA being able to push stored procedure code directly to production needs to be abandoned.  The database schema scripts need to be completely under source control and part of the automated builds.  Changes to stored procedure code or DDL scripts should not move to production until it's been through a successful integration run of the CI infrastructure.  Again, back to first causes.  You want reliable traceability between the version of the middle tier and the version of the database schema.  Continuous Integration with the database can be tricky. I'm more than a little intrigued by the Ruby on Rails database migrations, even for non-Ruby development.

    Automated Unit Tests

    My experience is that good unit tests help maintainability immensely.   On the other hand, unit tests that are brittle, hard to understand, and too tightly coupled with the implementation may only make things worse.  If you're afraid to change the code because too many unit tests will break, you've got some serious problems in either your tests or the code structure (brittle unit tests are a code smell).  Writing good unit tests is a very large topic in and of itself, but suffice it to say that it behooves you to spend some time learning more about writing good unit tests.  I suspect that a lot of the failure stories we see from people trying TDD result from not understanding how to write good unit tests.

    • Providing a solid safety net of regression testing to enable refactoring.  While I think refactoring is necessary to arrive at a good design in any situation, refactoring is an absolute necessity as the function of an application evolves.  For instance, in my current project I needed to reuse some large pieces of functionality from the application in a completely different context.  The first thing we did was to refactor the code so that we could call the smaller pieces of functionality without the application workflow as a whole.  The only reason we were able to do this refactoring safely was a series of FIT style tests we had written as regression tests.  We made a series of small changes and ran the test suite after each change, occasionally backing up to reverse a code change when a test failed.
    • Creating a specification for the usage of each class with readable examples of the API.  If the test is readable, it should act as documentation for the code that it exercises.  I often refer to unit tests to see how to use class that I didn't write.  We rely very heavily on a multitude of open source tools that are notorious for a lack of documentation.  In several cases I've been able to pop open the code and read the NUnit tests to discover how to use a feature.  Unlike an external document or even NDoc style comments, the best thing about making unit tests act as documentation is that the unit tests cannot diverge from the code without failing.

    Both the specification and regression safety net qualities of TDD are maximized by creating fine grained tests that are easily understandable.  When we inherited our legacy application last fall all that came with it were a series of coarse grained integration tests that would fail without any useful failure messages.  It was almost impossible to troubleshoot the tests without putting a debugger on the code and following it from end to end.  Those tests did not aid in refactoring because they didn't really diagnose a problem, only report that there was a problem.  Over time we've moved to FIT style tests that exercise smaller pieces of functionality at a time that are easier to control.  These tests have been far better as a safety net because they can give us much more context around the exact reason for the failure.  In our newer code we've written TDD style from the beginning a failed unit test will point to a very small area of the code, making the diagnosis for the cause of the failure much easier.

    I haven't internalized it completely, but I definitely like where the Behavior Driven Development (BDD) advocates are going.  Even if BDD leads to nothing but writing unit tests with cleaner syntax I would call it a success.  The slight shift in semantics from "Test-" to "Behavior-" is important.  I think we will be better off when the emphasis is more on creating an executable specification of the expected behavior in the small versus "at some point I need a unit test for each method on each class."  TDD/BDD is supposed to be an exercise to define what the code is supposed to do and then ensure that the code does lead to the expected results.

    Executable Requirements for Less Expensive Regression Testing

    Code isn't useful unless it does what it is meant to do and continues to do what it is meant to do.  Assuming that you actually have the correct requirements from the business, what's the best and most efficient way of verifying the code against the requirements, now and later?  After all, regression testing is one of the most expensive items in software maintenance.  One answer is to implement the detailed requirements as automated tests.  The obvious benefit is that running the tests ensures, or at least detects, that the code still fulfills the requirements.  Automated tests as a requirement document also has a significant advantage in that it reduces duplication between a requirements document and the testing plan.  Instead of keeping two sets of documentation synchronized with each other and the code, you have one source of information that can be automatically reconciled against the code.  Another huge advantage of specification by automated test is the removal of ambiguity from the requirements.  A test succeeding or failing is a binary decision, there's no room for ambiguity the same way there is in fuzzy "the system shall..." type requirement documents.

    Okay, the first couple of objections to automated tests as requirements are that non-developers won't be able to understand or write the automated tests.  Not true.  Personally, I'm a big fan of expressing detailed requirements in FIT style automated tests.  You can write FIT tests that are human readable by non-developers (not that developers are non-human, but...), especially since you can quite happily mix prose with the test tables.  FIT tests used to be limited to table driven tests, but with the addition of the "flow" style test fixtures in the FitLibrary you can effectively write automated tests in English sentences.

    Test automation isn't going to be a silver bullet, but it goes a long, long way to enabling change in software systems -- especially when the tests are run automatically as part of your Continuous Integration tests.  If you can catch regression bugs by the automated tests almost immediately upon checking in the code, you can usually fix them faster.  I definitely think that you can turn bug fixes around much faster when you can take care of things completely on your own workstation without having to go through the formal bug workflow.  Test automation is especially effective when the developers can execute the tests on their own workstations.  That can cut the feedback cycle down immensely.

    Badly written automated tests can even cause more effort and trouble than gain.  The same qualities of a good unit test apply just as much, if not more so, to acceptance testing.  Good automated testing does not automatically equivocate to FIT either.  The key point is to create tests that are easy to understand, reviewed, and hopefully written by the business experts.  Ruby or Python scripting seems to be another alternative for testers to create readable, automated tests.

    How about Documentation?

    I haven't mentioned much about the type of system level documentation that needs to exist.  To be honest, if you engage me in a conversation about how to make a software ecosystem maintainable I would probably forget to even talk about documentation.  We've all heard the mantra "the code is the documentation", and I actually believe that, but with some additions.  Ideally, I think that comprehensive "documentation" for a codebase is this troika:

    1. Intention revealing code
    2. Solid automated test coverage
    3. A complete automated build script.

    As I mentioned earlier, the automated build scripts should be able to set up a clean development environment to run the application.  If that is really true, then the build automation script is the single most authoritative source of information about the required environmental setup for the application or system.  Even better yet, the build automation script can't diverge from reality if its being run constantly.  The same thing applies to automated acceptance testing.

    So, back to the question of what documentation do you need?  I say just enough to fill in the gaps between the code, the build script, and the tests.  The big danger of documentation is the risk or overhead of keeping the documentation synchronized with the current state of the code.  If documentation simply duplicates information that could be gleaned from the code, I don't think it's worth writing.  I fall into the camp that says the overhead cost doesn't justify the effort of comprehensive documentation.  More succinctly, I put a much higher priority on readable code, readable automated tests, and solid build automation than I do on documentation.

  • Developer/Tester colocation, what a novel concept

    Okay, I've never bought into the idea that the testers have to be aloof and independent from the developers to maintain objectivity.  We're all on the same team, and frankly, I find the idea that only testers are concerned about quality to be somewhat insulting.  Quality and productivity should be everybody's goal on the team.

    In terms of tester interaction, I'd say that I've worked projects that fall into three patterns:
    1. The testers are attached to the project, but don't sit with the developers
    2. The project had no testers.  Depressingly and stupidly common out in the world.
    3. The testers are colocated with the developers in a common area
    Guess which arrangement I think is by far the most effective for producing quality code?  My tester is sitting across the table from me working through exploratory testing on the ASP.Net portion of our current project.  Any time he finds a problem with the website he's calling me over to show me exactly what's going on and how it should work.  I don't have to waste anytime going back and forth writing emails, or phone calls, or comments in a bug tracker trying to understand the issue.  Because of the open line of communication from him to me I've been able to rapidly close down the UI glitches.  The faster we can close down problems the more time he can devote to deeper exploration and edge cases.

    On the other side of things, I'm available any time he has a question or is blocked.  In an iterative environment, or in our case a QA resource-constrained environment, the development team has to make a conscious effort to keep the testers fully utilized.  You cannot afford to have your testers idle for 3/4 of the iteration only to work heavy hours at the end.  If they need some code migrated, an environment problem resolved, or a question answered, it's probably more efficient for the team as a whole for you to stop coding and get the testers moving again.  In my case, I've been able to answer some questions about the way the code works that have stopped my tester from spending time on some unnecessary or invalid test cases.  It goes back to Mary Poppendieck's talks on the fallacies of point optimization.  In other words, oftentimes me going slower to make him go faster makes the team as a whole go faster.

    We do a lot with FitNesse testing, and we gain a lot of value from that FitNesse testing, but there's no way FIT style testing will be effective without very close collaboration between the testers and developers (and customers, but you just never get everything you want).  I can't find the link anymore, but I read a great, great post a couple years ago called "the power of the almighty Yo!"  Simply put, as much as possible you want questions about a system's code, tests, or requirements to be accessible by a quick "yo, how are we...?" query.

    Previous to having a tester on our team and in the office, we worked with a tester that was remote to us in a different office.  The tester's primary mode of communication was the (awful) bug tracking system.  That's horrendously inefficient and it flat out sucked.  Please don't take this to mean that I'm saying not to use a bug tracking tool, just that the face to face communication is more efficient for solving and fixing bugs.  Bug tracking is an auditing process, not an effective means of communication.  Of course some of the reason I'm down on the bug tracking tool is how much we all hate the particular tool we use.

    P.S.  If you've got a good tester like I do, go give him/her flowers/donuts/etc. immediately.  They seem to be rarer than good developers.
  • K. Scott Allen lists 7 Virtues

    Nice post here: 7 Virtues for Software Developers

    Adaptability:  My tester and I finding a way to make headway when the gremlins short circuit our integration server.  Quote from our overworked IT support guy today:  "It's fine until you try to copy anything from the network to the virtual machine"

    Resilience:  Pushing through environment issues every single day. 
  • Best of the Shade Tree Developer (with actual links!)

    Between being extremely short handed at work, tech' reviewing a new book, a possible book proposal of my own, and an unsettled situation at work, I'm having a hard time finding time to create new content for the blog.  So to bide time and fill a request for a rehash of some of my older posts, here's a "best of" compendium from the last year and a half.  New stuff will appear soon-ish.

    Things I Wished I had Known 3 Years Ago

    Test Driven Development

    Dependency Injection and Inversion of Control

    Mock Objects

    Design Patterns

    Agile Processes

    Testing

    Bending Legacy Code to my Will

    Random

More Posts

Our Sponsors

This Blog

Syndication

News

All opinions expressed here constitute my (Jeremy D. Miller's) personal opinion, and do not necessarily represent the opinion of any other organization or person, including (but not limited to) my fellow employees, my employer, its clients or their agents.

About Me

"Best Of" Compendium

StructureMap (Dependency Injection for .Net)

StoryTeller (Supercharged Fit)

Build your own Cab

TestDriven

MVP