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

June 2006 - Posts

  • Stuff coming up at the Shade Tree Developer

    If you're in Austin, I'm doing a presentation on StructureMap at the ADNUG meeting on July 10th.  I'll post the slide deck and the coding samples afterwards.

    Mea culpa, I've gotten really far behind in answering emails the last couple of months.  A majority of the non-StructureMap questions I get are related to the Model View Presenter pattern, usually in ASP.Net.  To catch up I've started to pull together a longish post on the MVP pattern that'll be ready within a couple weeks.  Just as a start I'm going to talk about:

    • I'm going to try another stab at explaining the MVP pattern
    • Where should user input validation go?
    • Do you (always) need a service layer?
    • View visibility to the model
    • Putting the "M" in MVP
    • View/Presenter interaction
    • Dealing with security and user preferences
    • Is it time to give the Front Controller pattern very serious consideration?

    There's a bit of a paucity of information on the MVP pattern, so I'll try to fill the gap a bit.  If there's an MVP related topic you'd like to see addressed, put a comment on this post and I'll see what I can do.

    In the mean time, here's some resources for MVP related stuff:

  • The Humble Dialog Box by Michael Feathers
  • Model View Presenter from a forthcoming book by Martin Fowler.  This'll be on my bookshelf the day it comes out.
  • Mike Mason on MVP with ASP.Net 2.0
  • Test Driven Development with ASP.Net and the Model View Presenter Pattern (Me)
  • More Thoughts on Model View Presenter (Me)
  • A Simple Example of the "Humble Dialog Box" (Me)

    In no particular order, I've got a couple longish posts outlined or envisioned:

    • Jeremy's Fourth Law of TDD -- Avoid the Long Tail
    • The Composite and Visitor patterns
    • Iterator, Memento, and Observer patterns
    • A couple of little mock object posts I've put off for a long time
    • Cleaning up Legacy Code
    • Pretending Javascript is a real language -- TDD with JSUnit, OOP with Javascript using Prototype, Rubyesque "Mixins" with Javascript
    • Creating a Testing DSL with the FitNesse DoFixture
    • Driving Selenium from FitNesse

    Way, way, way out is a comparison of Ruby on Rails against ASP.Net for software lifecycle issues

     

  • So How do You Introduce TDD into an Organization or Team?

    There's an interesting thread going on in the Yahoo TDD group started by Jay Flowers about introducing TDD to a large organization, and an interesting spawned thread about TDD without OO.  I made a tangential post last week that's referenced - How much design before unit testing, and how much design knowledge before TDD?

    I'm not necessarily the best person to ask about this because my experiences haven't been typical.  More than anything else, the critical factor is an organization or team of developers that is open and willing to doing TDD.  Management support would be nice, but in the end it's the developers who are going to make it succeed or fail.  I had it relatively easy because my introduction to TDD came as a consultant with ThoughtWorks, one of the leaders of all things Agile.  A key factor for me joining my present employer was their desire to adopt Agile processes.  The support for TDD was there from day one, at least from development management.  Add in a group of developers who all believe in TDD, or at least willing to give it a try, and you can achieve a pretty smooth adoption rate.   

    Coming in and laying down the law, "You will do TDD starting now!" probably isn't going to fly.  Actually, I'll say it more strongly -- TDD won't take without some kind of consensus from the developers.

    You might not have it that easy, but then again, the wealth of free information and TDD lore that's available on the web and blogosphere dwarfs the information that was available way back in 2003 when I was writing hopelessly tangled tests.

    A team bootstrapping itself will probably struggle at first.  For best results, you're going to have to get some coaching to the TDD newbies.  Most people, myself included, struggle at first with TDD when you start encountering nontrivial enterprise development tasks like persistence and integration.  Brown bag sessions (especially if the team takes turns presenting) and training will help, but for my money, you want a sprinkling of people who can coach TDD and get them paired up with the folks who are new to TDD.  If you don't have anybody who can coach, it might be worth the cost of bringing in an external TDD coach to work hand in hand with the team members.  Regardless of the experience level that your team has, I'd very strongly consider adopting pair programming as a way to disseminate knowledge.  One of the advantages of pairing is the propagation of tricks -- IDE shortcuts, ReSharper features, and TDD idioms for testing code.  For example, I learned how to use NMock for unit testing with external dependencies by pairing with someone who was familiar with NMock.  As a direct result, I immediately got faster.  Even part time pairing will help. 

    Going in to TDD, you're going to want an open-minded attitude about the way you code.  In other words, start with an assumption that you will have to change the way you code and design software.  I certainly wouldn't say that TDD is the silver bullet for perfect software designs, it's more like a white glove test that finds all of the tightly coupled dirt and gunk in your design.

    Learn Unit Testing First, or Go Straight to TDD

    Jay Flowers talks about two camps in his organization, one camp who wants to concentrate on doing unit testing first before moving to TDD while a second group wants to go directly to TDD.  My vote is actually on going straight to TDD.  Most of the challenge in writing automated unit tests is writing code that can be tested.  Putting an emphasis on writing the unit tests first keeps testability (and hence a strong focus on loose coupling) in the forefront of a developer's mind.  Retrofitting unit tests to existing code is hard, period. 

    OOP as a prerequisite for TDD

    On the Yahoo thread I linked to, several folks are challenging my opinion that TDD requires OOP by sharing positive experiences of doing TDD and Continuous Integration with VB6.  I'll take their word for it, but doing TDD with VB6 reminds me of seeing an elephant fly in Dumbo.  You can write almost-OO code with VB6 (minus true inheritance), but as I recall you fight against the language quite a bit.  VB6 wants to be procedural.

    Let's pretend functional programming doesn't exist (because I don't know enough about it anyway) and that SOA is largely an orthogonal concept to OOP.  I'm on record as saying that a solid understanding of Object Oriented Programming is essential for succeeding with TDD on nontrivial applications.  I wouldn't hesitate to recommend TDD to someone who is new to OOP, but sooner or later that same newbie will be held back if they're not a strong OOP developer.  To defend that assertion, let's go back to some first causes:

    1. I want to be able to quickly write small unit tests that can be coded in rapid order to maintain flow
    2. External dependencies can make unit testing much more difficult and/or laborious, not to mention slower

    The two primary ways to create testable code is to be religious in assigning responsibilities to different parts of your code and isolating external dependencies behind swappable interfaces.  In both cases I think OOP has a far better story than purely procedural programming.  OOP allows you to use techniques like Dependency Injection and Mock objects to replace external dependencies with fake objects that behave in a known way.  Using best OO practices leads to code that is cohesive and loose coupling, exactly the kind of code that is easier to unit test.  What can you do with procedural programming?  Function pointers?  You're certainly not going to be able to do the same kind of interaction testing we use with OOP, and that's an awfully important tool to leave behind. 

    The real enabler of TDD is writing code that exposes seams to unit test in small chunks.  As long as you can accomplish that goal, you're going to be fine.  I just think that coding in an OO style gives you many more tools to create testable code.  If you can stay perfectly in the realm of state based testing and keep your functions small and relatively independent, I can see you being able to use TDD with procedural code.

    Just for background, here's an older post on State vs. Interaction Testing.  You'll want to be able to do either, and know when each is most applicable.

     

    P.S. I think doing TDD is actually a great way to learn OOP.  You get immediate feedback on your OO structure.

  • What's your CruiseControl.Net sound theme?

    Just out of pure whimsy, what is everybody using for their CCNet tray sounds?  My team made a pact to change all of our sounds over this week because the old sounds were getting old.  I'd had Firefly/Serenity sounds for 3 months.  Between different systems, subsystems, staged FitNesse builds, and testing deployments we're up to about 20 different builds.  By my reckoning we have:

    • The Matrix (me)
    • Simpsons - classic, never gets old
    • Animal House
  • NSpec - BDD comes to the .Net world

    I saw this this morning --> http://nspec.tigris.org/

    If you haven't seen it before, think of Behavior Driven Development as a refined version of TDD.  Most of the writings on BDD I've seen so far are geared towards Ruby and other dynamic languages.  I honestly didn't think this was going to be possible in a static language.  I'm definitely going to give NSpec a spin soon.

  • TDD with Loops and Aggregated Operations

    This was originally meant to be an example in the Test Small Before Testing Big post.  Several months ago I overheard a developer pair on the other side of the room struggling over how to write a specific unit test.  When I wandered over to take a look the cause of their distress was easily apparent.  They were coding a testing harness that would sweep a two deep hierarchy of folders and perform a set of actions on each file the code found in each folder and subfolder.  They ran into trouble because they were coding from the top down.  The initial test was going to run from the entry point of the code all the way down.  They bumped into a couple common issues:

    1. They simply bit off too much to chew in one unit test.  One of the most common causes of a difficult unit test is that it covers too much scope.  In their case they were trying to put together test data for the folder/file hierarchy and a set of expectations to verify possible outcomes of each file.
    2. Far too much test data setup.  Copious amounts of test data setup is a code smell (read this section on the original Wiki).  By and large, if you feel like a unit test is dominated by mock object setup, it's usually time to split the code into smaller chunks.

    In this very common development scenario, I think the most important thing for a TDD practitioner to remember is to "code from the bottom up."  In my colleague's case, they followed my suggestion to test the actions on a single file first, then tested that the folder system iteration separately and began to more forward.  Divide and conquer.

    I hit a similar situation last week in our current project that I think is a good example (and doesn't require any real obfuscation).  I was coding a class called BundleTranslationRequestAgent that runs in our little messaging broker service to find new "Bundle's" and try to send a message to an external web service, audit the activity, and persist the state of the Bundle objects.  Starting from the bottom, I wrote and tested the code to handle a single Bundle object.

            public virtual void ProcessBundle(Bundle bundle)

            {

                // Create the outgoing Data Transfer Object

                BundleMessage message = new BundleMessage();

                message.ControlNumber = bundle.Id;

     

                // BundleTranslationService is a proxy to an external web service

                BundleMessageResponse response = _translationService.PublishBundleBytes(message, bundle.PdfContents);

                if (response.Success)

                {

                    bundle.LogSuccessfulSubmission();

                }

                else

                {

                    bundle.LogSubmissionFailure(response.FailureMessage);

                }

     

                // Update the status

                _repository.Update(bundle);

            }

    Working with just this small slice of the functionality, I wrote unit test cases for both success and failure cases and used mock objects to test the interaction from the agent class to the web service and the backing BundleRepository for persistence.  Here's a sample of a "happy path" unit test that uses RhinoMocks to mock the underlying services:

            [SetUp]

            public void SetUp()

            {

                mocks = new MockRepository();

                repository = (IBundleRepository) mocks.CreateMock(typeof (IBundleRepository));

                translationService = (IBundleTranslationService) mocks.CreateMock(typeof (IBundleTranslationService));

     

                agent = new BundleTranslationRequestAgent(translationService, repository);

            }

    Now that the class under test is set up with mock objects in place, run the actual test:

            [Test]

            public void ProcessHappyPath()

            {

                Bundle bundle = new Bundle(1234);

                bundle.PdfContents = new byte[] {1, 2, 3};

     

     

                BundleMessage message = new BundleMessage();

                message.ControlNumber = bundle.Id;

     

                BundleMessageResponse response = new BundleMessageResponse(bundle.Id, true);

     

                Expect.Call(translationService.PublishBundleBytes(message, null))

                    .Return(response)

                    .Constraints(new Equal(message),

                                new Equal(bundle.PdfContents));

     

                repository.Update(bundle);

     

                mocks.ReplayAll();

     

                agent.ProcessBundle(bundle);

     

                Assert.AreEqual(BundleStatus.Sent, bundle.Status);

                BundleAudit audit = bundle.GetLastAudit();

                Assert.AreEqual(BundleAudit.SUBMITTED, audit.Action);

     

                mocks.VerifyAll();

            }

    Note that I made the ProcessBundle() method public and marked it virtual, more on this later.

    The next step is to proceed to the looping operation.  In this case I need to test that the BundleTranslationRequestAgent makes the right query to the underlying BundleRepository object and processes each Bundle object that is returned from the repository.  There are some additional unit tests to check exception cases, i.e. the web service blows up.  I've already tested the processing of a single Bundle, so to simplify the unit testing of the aggregate operation I'd like to remove the single Bundle processing somehow.  The solution that I used in this case was to create a subclass of BundleTranslationRequestAgent that overrides the ProcessBundle() method with a mock method.

        public class PartialBundleTranslationRequestAgent : BundleTranslationRequestAgent

        {

            private ArrayList _bundles = new ArrayList();

     

            public PartialBundleTranslationRequestAgent(IBundleTranslationService translationService, IBundleRepository repository) : base(translationService, repository)

            {

            }

     

            public void ExpectBundles(Bundle[] bundles)

            {

                _bundles.AddRange(bundles);

            }

     

            public override void ProcessBundle(Bundle bundle)

            {

                _bundles.Remove(bundle);

            }

     

            public void AssertAllBundlesWereProcessed()

            {

                Assert.AreEqual(0, _bundles.Count);

            }

        }

    Now, to unit test the aggregate operation I actually test against this new PartialBundleTranslationRequestAgent class with the mocked ProcessBundle() method.

            [Test]

            public void ExecuteHappyPath()

            {

                BundleQuery query = new BundleQuery();

                query.Status = BundleStatus.New;

     

                Bundle[] bundles = new Bundle[]{new Bundle(1), new Bundle(3), new Bundle(4), new Bundle(5)};

     

                translationService.TestAvailability();

     

                Expect.Call(repository.Query(query)).Return(bundles).Constraints(new Equal(query));

                mocks.ReplayAll();

     

                PartialBundleTranslationRequestAgent partialAgent = new PartialBundleTranslationRequestAgent(translationService, repository);

                partialAgent.ExpectBundles(bundles);

     

                partialAgent.Execute();

     

                mocks.VerifyAll();

                partialAgent.AssertAllBundlesWereProcessed();

            }

    In this unit test I simply told the instance of PartialBundleTranslationRequestAgent which Bundle's should be processed, and asked it later in the unit test to verify that every single Bundle was passed into its ProcessBundle() method.

    This wasn't a very complex example, but even so, tackling the nested operation first before worrying about the aggregated operation kept each unit test small and easy to write.  One of the keys for frustration-free TDD is to learn how to create small unit tests that can be solved in rapid order.  If I had tried to write and unit test this code from the top down I could have easily gotten bogged down in much larger tests that would have been much harder to write and read.  One way or another, coding always comes down to "Divide and Conquer."  You've just got to remember the "Divide" part of the equation.

    Wrapping Up

    The trick of overriding a single method to create a unit testing version of a class isn't something I do very often, but it's handy in spots like a looping operation.  If the individual operation is complex I would probably split that operation off into its own class and use a mock object in testing the aggregated operation instead.  In the Bundle example I thought the code was simple enough that I preferred to just keep the code altogether in one place.  Many people would be upset about the ProcessBundle() method being scoped as public even though its really an internal operation.  In this case the clients of the BundleTranslationRequestAgent are calling it through the IAgent interface that has only the single Execute() method, so I really don't worry too much about the lack of a bit of encapsulation here.  My philosophy is to concentrate on making the code easier to test, and hence easier to complete.

  • Just so I can permanently retire this WTF story

    This post has little redeeming value and it'll be a bit mean-spirited, but there are some lessons learned at the bottom.  At the Agile Austin lunch today Joshua Flanagan and I were talking about how developers are sometimes slow to kick off limitations of the previous generation of technology.  We talked about people with VB6 backgrounds transitioning to .Net that simply can't make themselves create new classes because object creation was expensive in VB6 (I was that way too at first).  I brought up the classic problem of old school COBOL developers who code enormous procedures as a holdover from the days when COBOL subroutines were inefficient (I've always thought that I could spot a ex-COBOL programmer coding in any other language).

    That conversation inevitably led to a downward spiral of WTF stories.  Here's my clincher in the game of one-upmanship, and I hereby promise to never, ever tell this story again. 

    Several years ago I was involved with a Death March project to rewrite a very large VB6 application.  One of the VB6 client applications in the larger system was a target for a tremendous amount of user and production support venom.  I had all of the code available to me, so I decided to see if I could spot what the problem.  I looked at all the forms and didn't find anything but some innocuous looking code.  Then I accidentally opened the code for the splash screen(!) and out popped 10,000+ lines of VB6 code.  Here in all of its glory is what I found:

    1. The system had a large amount of metadata in about a dozen different tables that needed to be accessed in numerous places.  To standardize the access to the tables the developers created a shared DLL to provide access to these metadata tables.  It was a fine idea, but it was a terrible implementation.  They were already invested quite heavily in a homegrown CSLA code generation tool to generate middle tier code from the database structures.  I'm guessing that they adapted their codegen tool to create metadata classes and data access code for each of the tables.   The end result was an object hierarchy of an object for each row in the database.  Every single time you created an instance of the main metadata class you pulled down every single row (and there was a fair amount of data) in all of the metadata tables and created a VB6 object for each row -- every single time.  Remember that object creation in VB6 was much more expensive than it is in .Net today.  I'm not done yet.
    2. The collections of the metadata row objects were not hashed, and they didn't create any kind of find or query helper method on the main metadata class.  To access any piece of data you had to iterate through the collection to test each object until you found the one you wanted.  In some cases you had to loop twice if the first query didn't find what it was looking for.  The worst thing about this was the absurd number of times the looping code was copied and pasted all through the codebase.  I'm not done yet.
    3. In many cases the metadata class hierarchy was created on the application server and used remotely by a heavy VB6 client via DCOM.  All of that repeated iteration over the collections was done over a stateful DCOM connection via the WAN from the Pacific coast or South America to the US.  It gets worse.
    4. Back to the VB6 client that caught all of the flack.  In this case the metadata component ran on the client workstation.  That's right, client workstations hitting the database directly (and the connection string information on the workstations as well).  This particular database server had some severe instability problems due to the number of open database connections (this was back in Oracle 8.0.5 days when it wasn't as resilient to inactive and orphaned connections).  I'm not sure which is worse, long running stateful communication over the WAN or heavy database access on a client box.  Almost there.
    5. Lastly, the guilty VB6 client was some sort of polling mechanism that ran continuously.  Every .4 seconds a timer event would kick off the polling.  As you probably guessed, the client created a new instance of the whole metadata hierarchy every single time to look for the exact same piece of data every single time.  Every .4 seconds they had to pull down all of the data from the database, create all those COM objects, and iterate over the collections to find the exact same little piece of data.

    Okay, the maintenance team found the issue pretty quickly and largely fixed the instability of that one client rather quickly by caching the data.  At one time I was standing on a soapbox and defending the rewrite by saying that we could reduce the LoC count of the system by nearly an order of magnitude in some cases -- just by cleaning up the way the system fetched metadata.

    Lessons Learned

    The project was an awful experience, but I learned a lot about enterprise development.  For those of you who are new to large scale enterprise development, here's some things to know or think about:

    • Network round trips are evil.  At lunch today we were laughing at developers who obsess about optimizing string concatenation while writing systems that are chatty to the database.  The fastest way to bring an enterprise application to its knees is excessive network traffic.
    • If you possibly can, avoid stateful network connections.  Stateless connections will lead to better scaling and reliability.  For maximum scalability (and I'm thinking horizontal scaling here obviously), a client's request should be able to be serviced by any of the servers.  Then scaling can be accomplished by putting a hardware router in front of your application/web servers.
    • Be very cognizant of database connections. Newer database engines seem to be more resilient now, but you can easily overwhelm a database by opening too many connections. There's a reason why connection pooling and running data access through middle tier servers was, and is, important for databases. Make sure you understand how connection pooling is configured and working in your environment
    • Fancy SOA infrastructure or big Enterprise Application Integration strategies don't mean crap if the code underneath it sucks.  I know there's a pervasive theory that you *fix* the enterprise architecture by putting the ugly stuff behind abstracted web services first, then replacing the legacy code.  Granted I'm an application guy, but my money is on prioritizing the patching of the existing code. 
    • Just think about what you're doing.  I'm writing the same code over and over again, I should eliminate the duplication and then I'll code faster.  This seems like it's harder than it should be, maybe we should look for a different way?  This just can't be right to code this way -- it's not.
    • Severe schedule pressure leads to crappy code that'll be more expensive later.  I'm sure the developers of that system deserve some smacking, but I know that management put them in some extremely bad situations.  At some point I think you just have to scream no more in an impossible situation.  One of the legends I heard was the development team being given cots at the factory and being told to make the prototype run the factory floor in 6 weeks.  I seriously doubt I'd write quality code in that situation.
    • Maybe, just maybe, you shouldn't push a prototype into production, then willy nilly glom on more code for 3-4 years after that.
    • Maybe, just maybe, a mission critical system that is a foundation for your business shouldn't be built on the cheap.

     

    BTW, that (stabilized now) VB6 code is still limping along as far as I know, five years later and at least three major replacement attempts under the bridge.  The even older COBOL system that the VB6 system was supposed to replace was still limping along the last I knew of as well.

  • Breaking my Visio/Rose-less streak today

    I had to break down and install Visio this afternoon to create some UML sequence diagrams to send to an overseas development partner tomorrow.  That breaks a 3+ year streak without drawing a single UML diagram in any kind of modeling tool.  I'm planning to promptly begin  a new streak next week.

    I can't say that I have the slightest yearning to pick up Rational Rose or Visio again.  Put me in front of a modeling tool and I go into some sort of zombie mode - "must make class diagram line up better."  I'm still waiting for a modeling tool with the "Minority Report" interface.  Architecture and design isn't real unless somebody is waving their arms about.  Squinting at a Visio screen and fiddling with the mouse just doesn't give me the same architect astronaut high I can get from waving markers in front of a whiteboard in a room with poor ventilation.

     

  • How much design before unit testing, and how much design knowledge before TDD?

    In my Third Law of TDD Liang asked me in a comment:

    "Could you let me know if you have a basic application architecture design in mind before you write the first line of code? Or you just let the test/code lead you to a design. For example, if you find a common method in related classes, you create a Interface, just as you mentioned in validation. If that is correct, I think TDD is pretty hard for a beginner, since the developers should have very good knowledge in OO, design patterns, refactoring before beginning implementing TDD. otherwise how she/he know how and when to refactoring?..."

    To the first question, do I have a basic application architecture in mind before I write the first line of code?  That's a long conversation, but the short answer is that I generally have a basic idea for the application/system/service as a whole.  The longer answer that I've learned over the last couple years is to not commit to a design or a structure upfront.  As far as the unit tests leading the design and knowing when to refactor, you should constantly be queuing up design ideas and evolving your thinking about the design but the unit tests drive the actual direction.  You should only introduce an abstraction, either upfront or by a refactoring, when you know enough to say that the abstraction in question is valuable.  Creating an abstraction too early often leads to painful, sclerotic code.  Waiting too long is a wasted opportunity or a harder refactoring.  The key is to make design decisions of all kinds at the Last Responsible Moment.  Determining or recognizing that last moment is something that comes with experience.

    To the second question, how much should you know before you start using TDD?  I'd restate that question to a more generalized "how much do you need to know to start programming?"  While TDD does have some of its own lore (xUnit, mocks, Dependency Injection, IoC, etc.), for the most part the knowledge and skills that enable effective TDD are the same skills you would want to do more traditional upfront design work.  You've got to look for a coding and design style that fits with your own thinking style to balance upfront thinking and reflective thinking.

    I would, and do, recommend TDD for new developers.  You have to start at some point and it certainly helps to get some coding experience under your belt before you tackle subjects like Design Patterns and Code Smells.  I think the value of TDD for a new developer is the concentration on verifying code in small chunks for the rapid feedback.  Another advantage of using TDD as a beginner is the emphasis on simplicity as you layer on functionality little by little and introduce abstractions slowly. 

    You simply can't ever code blindly without thinking for very long, so you might as well get used to working reflectively.  Many of the worst application architectures I've seen (and one that I did) were a result of determining abstractions upfront that didn't really work, and then compounding the problem by bludgeoning blindly ahead.  Regardless of your style, you absolutely have to continuously reflect on your design as you work, both to spot design flaws and opportunities for better designs.

    To be honest, I think TDD is more difficult for someone with a lot of experience in upfront design techniques.  I had to "unlearn" quite a bit before I became effective with TDD.  The developers I've worked with that started in Agile shops seem to have an easier time within an Agile process.

    As far as what you need to know to be effective with TDD and continuous design in general, the more you know the better you'll be.  I'd start with basic OOP best practices with something like the Craig Larman book.  Learn about code smells and Anti-Patterns to recognize when your code is moving in a bad direction.  Studying design patterns will help you inside of any design technique or development process.

    Here's two absolute truths about an individual's coding:

    1. Everybody works differently
    2. Results will improve with more experience and more knowledge
  • Test Driven Development is merely one part of a well-balanced diet

    Some ranting ahead...           ...or just skip to the second paragraph

    I've never, ever seen any serious minded person claim that TDD is a silver bullet that's going to stop all defects from getting into your system.  I have heard some people say TDD doesn't work because it's impossible to test the user interface, or threading code, or performance.  Even assuming that that's all true, and it's not by the way, does that really mean you shouldn't bother with TDD?  A couple months ago there was a bit of a flame war going on kicked off by Rocky Lhotka's remarks about TDD on DotNetRocks.  A frequent point of severely flawed logic from the non-TDD camp was "yeah, but TDD doesn't eliminate the need to test your code."  Agreed (duh), but in what possible way does this statement invalidate TDD as a practice?  Compiling doesn't find every possible coding problem, so should we throw that out too?  The last straw was a troll-ish comment on my blog yesterday that went something like "ha ha, you had a bug, guess that TDD stuff doesn't work after all.  LOL!"  Ignoring the irritating puerile nature of the comment, it's a myopic point of view.

    In Steve McConnell's Code Complete (v2) book there is an entire chapter devoted to removing defects from code where he compares the effectiveness of a variety of practices in detecting and removing defects from code.  As I recall from the book, no single technique was shown to find all problems, or even most of the defects.  McConnell's strongly stated advice was to combine as many practices as possible to create a layered defense against defects.  That stack of practices could look like this:

    • JAD sessions - we don't do nearly enough
    • Whiteboard designs to work through design and architectural issues
    • Simply compiling your code find syntax problems
    • Test Driven Development
    • Integration tests
    • Pair Programming / Code Inspections / Code Reviews
    • Demo screens in progress to users
    • Acceptance Testing with FitNesse, Selenium, etc.  By being able to run automated tests on a developer workstation, we won't even turn over code to testing until all of the acceptance tests pass. It's a much, much faster feedback cycle than just throwing the code over the wall to testers.
    • Continuous Integration - finds environmental setup issues, tests the deployment, continuously ensure the code compiles and all of the automated tests run successfully
    • Manual Testing - no you can't eliminate manual testing, but you should leave that for creative "exploratory" testing
    • User Acceptance Testing - I've always thought it was really dumb to leave this strictly for dead last, but...

    None of the practices solves every problem, but the combination of the practices is powerful.

    A nearly indisputable piece of software lore is that defects are cheaper to fix the sooner they're detected.  Think about this for a second, how much faster and easier is it for you to fix a problem if you find it when you're coding initially versus letting a tester find and file a bug report?  How much more bureaucratic crap do you have to go through to fix an official bug versus simply changing the code on your own workstation?

    One of the specific areas of value provided by TDD is the rapid feedback on your code.  TDD is more effective than just plain old unit testing.  Real "Test First" TDD gets unit testing injected earlier, and more thoroughly, into the coding lifecycle.  Leaving unit testing for later can easily lead to code that's difficult to test (I've seen this happen far too many times), and it's difficult to go back over every case.  The problems that are exposed by TDD can be fixed fast and immediately, long before a tester gets a hold of the code or you get stuck in marathon debugging sessions.  If nothing else, you can provide more time for the testers to work through the harder, exploratory testing instead of writing up endless bug report lists for mainline business functionality.

  • Acceptance Testing - "Under the Skin" or using Stubs, is that Okay?

    From Why and When to Use Mock Objects, Mike Bria asks:

    ...So, my thought (and question) is this: is it okay for me to apply "faking" principles at a service level? In other words -- I'm testing service 1, which, among a bunch of other little details, queries service 2 and posts a command to service 3. Can my test fake Service 2 & 3 and still be a valid Acceptance (or, what I prefer to call "Service") test?

    My opinion is yes, and we've done this ourselves to great effect in a certain situation earlier this year.  More importantly, I talked it over with a couple of our senior testers and they agreed with some caveats - you should only do this with particularly troublesome services, and you absolutely have to do some level of end-to-end blackbox testing.  The experience with the blackbox testing should be used to fine tune the whitebox tests that use the stubs to create more realistic tests.

    Our Experience

    Earlier this year my team built a new NT service to manage an asynchronous workflow between our large application, a brand new web service being built by a third party, and our backend messaging broker.  The messaging broker on the backend is notoriously difficult to work with, and we didn't have any clean way to test the results inside the broker.  The 3rd party web service wasn't ready.  In this case, we wrote a FitNesse DoFixture to simulate the interaction of our new service with its service dependencies.  We started the inner service controller class and used StructureMap to inject stub implementations of both the gateway interfaces to the 3rd party web service and the messaging broker.  It was undeniably successful.  Our tester was easily able to setup a wider range of failure conditions and edge cases in the interaction with the external dependencies.  We could simulate one of both of the dependencies being unreachable, or throwing exceptions on invocation to test the error handling of our service.  Most importantly, when we did attach the new service to the external dependencies for real it worked like a charm.

    Testing User Interfaces "Under the Skin"

    This might not be as true today as it was a couple years ago, but the conventional wisdom in Agile testing circles is to test user intefaces just "under the skin."  In other words, bypass the actual user interface and test against the controller layers first, then use manual testing against the full UI.  The easiest way to enable that strategy that I know of is to use the Model View Presenter pattern to write ultra thin views that can be replaced in tests.  An easy way to go about this is to write a FitNesse DoFixture class to test a single screen, and make the DoFixture class implement the "view" interface so it can catch all of the interaction from the presenter.  Here's a short example I coded yesterday to test a new summary screen:

        // Interface for the actual screen, implemented by an ASPX page

        public interface ISummaryView

        {

            bool GroupBySender { get; }

            bool GroupByReceiver { get; }

            bool GroupByStatus { get; }

            bool GroupByDays { get; }

     

            void SetRefreshTime();

            void Display(DataSet data, string[] fields);

        }

    The DoFixture will implement the interface above and pass itself into a presenter class to catch the interaction.

        public class SummaryScreenFixture : DoFixture, ISummaryView

        {

            private bool _groupByDays;

            private bool _groupByStatus;

            private bool _groupByReceiver;

            private bool _groupBySender;

            private DataTable _table;

            private string[] _fields;

     

            public SummaryScreenFixture() : base()

            {

     

            }

     

            public void GroupBySenderIsChecked(bool active)

            {

                _groupBySender = active;

            }

     

            public bool GroupBySender

            {

                get { return _groupBySender; }

            }

     

            public void GroupByReceiverIsChecked(bool active)

            {

                _groupByReceiver = active;

            }

     

            public bool GroupByReceiver

            {

                get { return _groupByReceiver; }

            }

     

            public void GroupByStatusIsChecked(bool active)

            {

                _groupByStatus = active;

            }

     

            public bool GroupByStatus

            {

                get { return _groupByStatus; }

            }

     

            public void GroupByDaysIsChecked(bool active)

            {

                _groupByDays = active;

            }

     

            public bool GroupByDays

            {

                get { return _groupByDays; }

            }

     

            public void SetRefreshTime()

            {

                // no-op;

            }

     

            public void ClickRefresh()

            {

                SummaryPresenter presenter = new SummaryPresenter(this);

                presenter.CreateDisplay();

            }

     

            public void Display(DataSet data, string[] fields)

            {

                _table = data.Tables[0];

                _fields = fields;

            }

     

            public Fixture FieldsAre()

            {

                return new SummaryFieldFixture(_fields);

            }

     

            public Fixture DisplayIs()

            {

                return new SummaryDataFixture(_table);

            }

        }

    In combination with other fixtures that setup the database state, I was able to quickly test the screen controller logic without ever starting up IIS. We've used this strategy in the past extensively with reporting screens to test sorting, filtering, and querying functionality. These tests will always be faster to setup and run than a test that executes through the full web screen.

    Sorry about the very slow response Mike

    - Jeremy

  • Jay Fields offers some observations on TDD

    One of my favorite bloggers, Jay Fields, has a great post up today --> Some Test Driven Development observations that's a great overview of learning effective TDD techniques and concepts with plenty of good links.

    I don't entirely agree with some of it (I'd call "one assert per test" and "one concrete class per test" guidelines rather than hard rules), but its a great, great read.

     

    Check out the follow up too.

     

  • A tear your hair out kind of bug

    I had a scary seeming bug this afternoon that I had to share.  Our tester showed me a problem with a web application not showing a valid option in a dropdown box on a staging environment server.  After totally dismissing my "we haven't changed that code in six months" line, 3-4 of us went to work to chase down the problem before we had to cancel an uncomfortably visible production rollout.

    First, check all the database data.  Everything checks out just fine.  The FitNesse regression tests are still humming along, so it's doubtful to me that the sql is the problem.

    We looked at a user specific configuration file and all of the necessary options were indeed there.

    I moved on to troubleshooting the remoting configuration.  We have a specific environment test for the remoting configuration, but maybe somebody ignored a reported failure or it didn't work.  Nope, the remoting was fine.

    I threw my hands up in disgust and went back to something else.  Our tester piped up in about 10 minutes with the underlying cause.  The system was looking for the user configuration file with the filename [User Number].xml.  The folder view we were using on the staging server was hiding the file extensions.  The real name of the file on the file system was [User Number].xml.xml.  Doh.

    My faith in reality and reason restored, production push on track, and there goes a good chunk out of an otherwise productive day:(

  • May the Flow be with You

    Another follow up to "Test Small, Before Testing Big"

    We're most productive when we are coding "in the flow," a mental state described as being fully immersed in an activity with a feeling of energized focus and success.  It's the state where code effortlessly flies onto the IDE screen and new unit tests pass with ease.  Frank Sommers questioned TDD as a possible impediment to the flow.  My experience with TDD has been much more positive than Frank's, but this quote sums it up for me:

    Writing code in a certain way can help us get in a state of flow. For flow to occur, we must work on a chunk of problem at the right complexity level - a problem that's neither trivial, nor overly complex. We must have a clear objective for what we're working on -- for instance a specific, well-defined use case. To receive immediate feedback, we must unit test the code as we write it. Finally, it should be possible for these small pieces of functionality to work on their own, without having to go through major integration steps.

    TDD "flows" when you can cycle very rapidly from writing a unit test to coding to success to writing the next unit test.  Small tests that are easy to setup and make pass result in effortless coding.  Big tests that take a long time to setup and lead to copious amounts of debugging can destroy the flow.

    I'd say that there are two primary challenges to TDD flow: 

    1. Creating right-sized unit tests
    2. Determining the next test to write

    One of the most pernicious problems that people have with Test Driven Development is "Writer's Block."  Difficulty in finding the first unit test to write for a coding task or finding the next unit test to write will slow TDD down to a crawl.  You can easily fall into an Analysis Paralysis trap where you're afraid to start coding because you don't yet understand how the whole picture fits together. My advice? -- forget about the whole and code from the bottom up.  Pick an easy task that you do understand and write an isolated unit test for only that small task.  Then pick another easy task and repeat.  The interfaces and usage of the small pieces can give you insight into the structure of the remaining pieces. 

    Okay Jeremy, how do I get started if I don't even know what the tasks are?  What do I do when I get stuck?  Yours will vary, but here's an incomplete list of my bag of tricks:

    1. Start with some upfront thinking.  The single most effective design technique for me is to simply make a list of the things that have to be built to code a user story.  It's too simple to be any kind of "Something Driven/Oriented Design," but it's often enough.  The key is to use the upfront design thinking to break down a user story into approachable tasks, and switch to TDD before you hit the point of diminishing returns.  Having the list somewhere close can help prod you to the next unit test.
    2. Use Responsibility Driven Development to come up with some candidate classes and methods to start with
    3. Doodle some UML sketches. 
    4. Do a throwaway spike.  It's far more productive than staring at the screen.  Sometimes the best thing to do is just write some code and see where it starts going, contingent of course on throwing that code away later
    5. Um, note to self, talk it over with one of your colleagues.  It's silly how many developers, myself included, simply will not think to ask somebody else or talk it over.

    Pair programming brings it's own set of challenges for establishing a TDD flow between the pair.  I've found that a pairing session is more successful when you can do Ping Pong Programming.  I write a unit test, then my pair takes over the keyboard and completes the code to make the unit test pass.  He will then code the next unit test and slide the keyboard back to me.  It takes a shared understanding of the task at hand and a little bit of time to get into that state, but when you do get there it's a very satisfying feeling.  One thing pairing should do is to help fill the gaps in one person's flow.  The backseater, i.e. the guy without the keyboard, should really be thinking about the next unit test while the other fellow is typing.  The best thing to say, and I'm not a poster child for this by any means, is to keep talking as you go.*

    Eric Hodel takes on the same subject from a different angle in TDD and Creative Flow.

    I've got yet another follow up on Test Small, Before Testing Big queued up about using mock objects to avoid context switching, writing smaller tests, and maintaining the flow.

    *Then again, it bugs me when my pair partner is talking a mile a minute to the point where I can't hear myself think.  You might want to go somewhere else for pairing advice;)

  • TDD and Debugging

    Just a quick follow up to my post on "Test Small Before Testing Big."

    It's relatively easy to sell the long term benefits of TDD is to make the code much easier and safer to modify, but in the short term the cost of writing all of the automated unit tests can appear to be inefficient.  Looking at the bigger picture of the project, Test Driven Development does not take longer because the extra time spent writing unit tests is more than offset by reducing the amount of time spent in the debugger and fixing bugs late in the project.  This point is often overstated in inflated claims but I'll repeat it anyway because I believe it and it's consistent with my experiences -- using Test Driven Development can significantly reduce the amount of time spent in debugging.  As a feedback cycle, TDD style unit tests are a shorter, more efficient way to discover and remove defects from your code than long debugging sessions.

    Notice that I said "can" and not "will" reduce debugging time.  The dramatic reduction in debugging time only happens when you're writing small unit tests against isolated code.  If you are trying to use TDD, excessive usage of the debugger is your code telling you that you need more granularity in your unit tests.  Stop and reflect on how you're coding the solution.  Is there a way to test in smaller chunks?  Is there a class, or classes, that are getting too big and taking on more responsibilities than they should?  Do you have a good separation of concerns in the code, or are some concerns getting a bit too intimate with each other?  Could I use a mock object to write smaller tests?

    It isn't just that TDD makes debugging unnecessary most of the time -- it also makes debugging easier.  To defend that statement, let's first think about why debugging can be difficult (not counting goofy third party components or web services outside your codebase).  The main difficulty of debugging in my mind is the number of variables at any given time, and by this I mean how many different places in the code could be the problem.  The number of variables will shoot up when you try to write large tests, especially when the smaller code units within the larger code structure have not been tested.  In this case the problem could be anywhere because nothing has been previously verified.  Going a long time without a test makes the problem worse because you could be going off in the wrong direction and not know it until you try to write a test.

    The way out of this situation is to apply the Third Law religiously by writing granular unit tests.  In a granular unit test that is failing there should be so little code to examine that you can usually spot the problem visually through inspection, or at least promptly when you debug into the unit test (unit tests are a great entry point for debugging, I simply won't code without TestDriven.Net installed).  For that matter, bigger tests are often much more difficult to understand, especially with a lot of mock object setup.  It's bad enough when the code isn't  working, a difficult to comprehend unit test on top of that certainly exacerbates the problems.  Maintaining a pace of writing smaller granular unit tests should make integration testing easier.  When you do move on to putting the little pieces together in bigger integration tests you have the confidence in knowing that the little pieces are already unit tested and any problems are most likely related to the new integration code (some of that will depend on how closely the interaction testing in the unit tests matches reality, but that's a big topic for another day).

    Another factor that can make code harder to debug, and this shouldn't be underestimated, is simply the length of time between writing the code and troubleshooting the code.  With Test Driven Development the verification of code is virtually simultaneous with the writing of the code. 

  • Nick Parker on StructureMap

    Nick Parker has a short (positive) evaluation of StructureMap up on his blog at http://developernotes.com/archive/2006/05/31/858.aspx.

     

    Thanks Nick.

More Posts

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