The single greatest programming I’ve ever received is this: Use test apps religiously. In this world of Nunit and the like, perhaps I should explain what I mean by test apps.
What is a test app?
A test app is simply the absolute smallest amount of code that you can use to create your new functionality in. In my case, I usually create a new Windows Application in Visual Studio and put starter code in the Load event. If I feel like being extravagant, I’ll drop a button on the form and put the hook code in there instead. That’s it; the entire framework of a test app.
A test app is meant for creating a single piece of production code. It lives for the code, it dies for the code and nothing beyond what is needed to support that code should *ever* be added. Do not fall into the trap of creating a complex test application that can handle dozens of bits of code. This is unmanageable and defeats its own purpose as the code in it gets further removed from the real code over time. Disk space is cheap, don’t be afraid to create a few folders.
Test apps are not to be confused with Test Harnesses and utilities like NUnit. These tools are invaluable but are meant to be applied to the full body of code. The test app is only for one little bit of functionality. If you really feel that compelled to use NUnit everywhere, you can wire it into your test app as well 🙂
Why a test app?
- Lightning fast revisions and compilation. Since the test app is so small, you can test, fix, build and retest several times in the same amount of time that it would take to compile the main application once.
- Fully Isolated debugging. If it breaks in the test app, you are 99.999% sure that it is your code. In the main app, things can break for an almost infinite number of reasons. Perhaps someone checked in bad code, the database is down, someone playing with test data – all of that is completely unrelated to the code you are working on but slows you down nonetheless
- You get full debugging abilities on the test app. This may not always be the case in your test environment. In many test environments, you are limited to writing to logs for everything. This is nice, but far less useful than a fully interactive debugger.
- Rich testing. You can define any number of scenarios to run your new functionality through simply by copy-pasting a line a code in the Shell and changing a parameter or two.
- Performance testing. If you suspect that your code might be a bit slow, it is trivial to set up loops and timers in the test app. I can run my code through millions of iterations with almost no effort in the test app. Try setting up this type of test in the main application and you will immediately see the perks.
- Easy experimentation with unproven algorithms. There are times that you need to feel your way to a solution in code. This provides a completely safe environment to do this in. Trying to do this in the main app often requires a lot of instrumentation in order to call the new logic, all of which might just have to be undone if the algorithm doesn’t work.
- Known code. Because of how small the test app is, odds are good that you will walk through the code in the debugger quite a few times while working on it. This simple task might be all but impossible in a shared dev environment.
- Generic. In creating your new code so that it can be tested in the harness, you might discover that it works just fine if you define a parameter as IEnumerable instead of ArrayList or a custom collection, making it potentially even more useful to the main application.
- There’s also a free bonus benefit at the end of the article
Here is where a little design goes a long way. Write your new logic so that everything can be passed into it as parameters (your eventual production code will thank you for this!). If you are creating a new class, provide constructors that allow it to be set up with enough information to make the class think it is in the real app. Ideally, your new code should know nothing about it’s environment other than what is fed to it.
ABOVE ALL – TREAT THIS CODE LIKE YOU WOULD PRODUCTION CODE. No hard-coded values, no assumed paths, etc. If you need something like that, pass it in as a parameter. This is so that you can literally copy-paste this logic into the larger application unchanged.
Create the absolute least amount of code needed in order to support the functionality you are about to write. By this, I mean hard-coded parameters and the like. The shell is meant to be dirty, ugly code that you wouldn’t show to anyone – even for money. Its sole reason for existing is to be a fast way to call your new functionality, not as a show piece for CodeProject.
Connect it up
Go back to your shell code and connect it to your new code. Does it work?
Test it and test it again
Once you have it working the way you want with the expected inputs, you can play all sorts of games with the code. Hard code values in the parameters to test out error handling or to simulate conditions that may be very hard to reproduce in the main application. Testing and fixing these sort of issues in a test app is easily 10 times faster than trying the same thing in the main application. The bigger the main app, the greater the advantage of using a test app
Drop it in
Since you went through the effort of putting your new code into its own black box of sorts by parameterizing so much of it, dropping it into your real application is almost a no brainer – just feed it the parameters it needs and you are pretty sure that you have a reasonably well tested body of logic in place.
Once you have the code in there, run it through it’s paces in the main app. If it breaks because you have, say, a null coming in where you hadn’t planned for one, its nothing to fix. Just create another test in your Test App to simulate the new situation and debug away happily. When fixed, drop the updated code back in again.
This is great and all, but isn’t this actually extra coding? Yes, slightly. The shell might take you 30 seconds to create if you type really slow, and then the main hooks another 2-3 minutes or so. The rest of the code you have to write anyway so you might as well do it in a place where you can beat the snot out of it easily and quickly.
No way, my test environment is SO complex. That might be true, but that doesn’t mean the functionality you are writing needs to know about it. Odds are good that it can be abstracted pretty easily. Not always, but more often than you might think.
I have written several thousand test apps in the 12 or so years that I have been using them and have never thought even one to be a waste of time.
Remember I said that you should never show your test app to anyone? That’s not entirely true. There is one person who will be *very* grateful for it – the guy who comes to you and asks: “How do I use your object?” You just toss him your test app and he can immediately see not only how to call it, but what you had in mind for it when you created it. For most of us, a quick example with actual code is worth a 1024 words.
Speaking of which, here’s a somewhat contrived example of a test app: