When you write a piece of code it needs to be as function-specific as possible. Therefore the monolithic code block that I've just described should be broken down into small routines. First of all there should be very little code behind a button-ideally, a call to another routine, but a small number of relatively safe commands is acceptable. If there is a need for a large amount of processing, there should be one procedure that controls the overall flow and control of the process, and that procedure calls function-specific routines. In the description I gave above, the database access should be a separate routine, as should the Registry code, and so on. This is good practice, and there is no taboo in having many small private routines attached to a form, a module, a class, or whatever. The testing is so much easier this way, and it also makes for much more specific error handling code. It's also tidier, of course.
It's important not to get too formal with the coding, though; we'd never get it delivered. The code that goes into making a Microsoft Windows application can be divided into two categories: process specific and interface specific. In very general terms the test code that goes into the process-specific sections is what needs to be planned beforehand because it's the process that actually gets the user's work done. The interface-specific elements of the system are still important, but they demand a much greater degree of spontaneous interaction from the user.
To illustrate the planning process I have invented the following functional specification for a small DLL, and I have included an associated test plan. Most real-world requirements will be more comprehensive than this but I don't feel that additional detail would add any extra weight to the point that I'm trying to get across. All software should be written from a functional specification (or design document, if you prefer). However, you'll find that if you write the test plan at the same time as (or immediately after) the functional specification, you will continually identify test scenarios that will prompt you to go back to the functional specification to add necessary error handling directives. This happened to me while I was writing the test script specification for the example DLL, even though it's only a simple demonstration.