Sunday, January 3, 2010

Test Soon Development: a case study

I didn’t sleep well after posting about my IoC-agnostic Prism Bootstrapper. Bad enough that I’d deleted the original Prism bootstrapper tests. I added insult to injury trying to justify it:

I really thought this simple task would take maybe a long day at most. Stripping the tests away from the start was supposed to help me stay in the time box. … I didn’t think I could afford [to maintain the tests]. … I wasn’t trying to deliver a product and there is limited value in tests for a version of the Prism RI that is (a) obsolete and (b) irrelevant to your application.

Except it didn’t stay simple and it didn’t take one day. My pride got the best of me. Ok I’m not proud of the code but I didn’t want to be ashamed of it either. So I kept polishing it. The scope expanded little by little; I added Autofac, Silverlight support, and the Prism RI. Four days flew by.

Now I’m invested. Now I care if someone uses it. Without tests I fear no one will. I can hear the scolding voice of Jeremy Miller: “I won’t even look at code that doesn’t have tests.”

I figured going in that setting up the test environment, converting the tests to run with new bootstrappers, and making it all presentable would take a day. That would have doubled the expected one-day project time. That’s ridiculous. So I deleted the Prism tests instead.

It seems a false economy now that it’s a four day project. There’s only a 25% cost if I actually can revive the tests in one day.  I can amortize it further if people use it, improve it, and extend it to more IoC containers.

One more day. How do I justify that? Perhaps, if I document the process and the outcome, I may discover something concrete to say about the utility (or futility) of testing. There’s a blog post in it at the very least.

...

I was right about the one day (see chronology below) … sort of. I squeezed it into one day only by taking shortcuts:

  • I worked exclusively with the Prism Bootstrapper test fixture. I did not restore tests for other Prism components nor for the Prism RI.
  • I revived tests for Desktop only; Silverlight testing would have sunk the budget.
  • I didn’t write any new tests. This being a refactor, the goal was to make all bootstrappers pass the existing tests … and prepare the ground for future tests.

Was It Worthwhile?

Automated testing, they say, confers specific benefits. Did I see them? Let’s review.

Find bugs: “No”. Ok, it revealed one minor omission, insufficient payoff for a day’s work.

Clarify intent: Yes. The tests give me a better understanding of what the Prism authors expected the bootstrapper to do. For example, they prescribed a particular launch sequence and wrote a test to verify that sequence. I may not know why the sequence is important. But were I tempted to write my own bootstrapper from scratch, I’d know to pay attention to this point.

Improve design: No. It did not stimulate design changes to the Systems Under Test (SUT). I still believe that writing automated tests fosters improves design. But I wasn’t writing tests. I was reviving them and, because my ContainerAdapter is supposed to match the UnityBootstrapper API, existing tests were unlikely to provoke change.

Increase confidence: Yes. While tests don’t prove that code works, they do provide strong evidence that certain things work “as intended”. Any evidence beats no evidence. The relief of seeing tests pass, especially tests someone else wrote, is undeniable. I’m hopeful that they improve your confidence in the product.

Speed testing: Yes. Before, if I made any change to the bootstrapping code, I had to run manual smoke tests for each bootstrapper variant. That entailed changing a compiler directive, recompiling, and running the app for each of five bootstrappers; the cycle took 15 minutes minimum.

The automated tests uncovered one bug. With their help, I fixed the bug and confirmed the fix for all bootstrappers in a couple of minutes. That’s a big improvement, for sure, but one speedier repair hardly makes up for the day I put into this. I’m twenty or so cycles away from break even. If someone finds more bugs, or decides to refactor the code to make it more readable or more capable, the payback will come.

Prepare for future  change: Yes. With a test regime and a body of tests, anyone can quickly verify that changes to the CalBootstrapper, changes to any ContainerAdapter, or the addition of new ContainerAdapters preserve certain constraints on how all of the bootstrappers work. Far from perfect to be sure – there are only ~25 test methods per bootstrapper – but we have a foundation in place.

Conclusion

The IoC-agnostic Prism Bootstrapper is better for having automated tests.

That said, the test development effort will be a total  loss if no one uses it. That’s a loss on top of the four days I’d have blown on it in the first place.

It’s a real risk. I am unlikely to use it myself, odd as that seems; Unity is working fine for my existing clients.

On the other hand, I suspect that the mere existence of tests improves the chances that someone will adopt it.

You’d think the IoC product authors might show some interest; their customers want to use Prism. As they improve their containers or come up with new versions, the test-backed CalBootstrapper should make it easer to demonstrate that their enhancements are Prism-friendly.

Patterns and Practices has a stake in promoting a Prism that is open to any container … and they are welcome to take this code and run with it.

Meanwhile, the payback remains uncertain. We’ll have to wait and see.

---

Chronology of Bootstrapper Test Resuscitation

5 minutes   Restore original Prism bootstrapper tests for the UnityBootstrapper; they pass.
1 hour   Refactor test fixtures so not dependent on Unity or UnityBootstrapper; they pass.
30 minutes   Extract Prism’s ancillary mocks (hand coded mocks) into their own assembly; tests pass.
1 hour   Get the tests passing on the “RefinedUnityBootstrapper”. No bugs found; one test failed on a log message change leading to a change in the test. Established a pattern for automated testing of multiple UnityBootstrappers.
2.5 hours   Tests finally pass on the first version of the IoC-agnostic ContainerAdapter. Almost all of the work involves decoupling the tests and mocks from IUnityContainer. Found one bug in my code! I had omitted a Region Behavior mapping. Now can automate testing of multiple Bootstrappers … as long as they require Unity.
30 minutes   StructureMap, the first non-Unity bootstrapper, is using my ContainerAdapter and passing the tests. There was no issue with the SUT. The trouble again was getting the tests to work.
7 minutes   AutofacContainerAdapter test project created, ran green, and checked in. That 7 minute pace augers well for extending the reach of the tests to forthcoming versions of SM and Autofac and to other IoC products.
30 minutes   Package, deploy, download, re-test both the bootstrappers and the Prism RI
~6 hours   Total