As an ex-IT developer on biiig computers I'm used to testing code modules thoroughly in a test environment set up to eliminate external influences such as hardware.
But I suspect that the concept of programming an Arduino is going to be a tad different, in that the hardware setup and code are tested together i.e. a hardware glitch can affect the test results.
Have I got this right, or is there a way to test code independently of the hardware before uploading it?
Regarding hardware glitches, I think we need to take a modular approach there too. Say you want to PWM speed control a motor: the hardware might consist of a transistor with resistor on the base, the motor itself, and a diode backwards across the motor. So if your speed control doesn't work, is it code or hardware, and if hardware, what: the motor or the transistor for example. Or the wiring?
Test plan might be just to put an led with resistor on the PWM pin and if the led is dimming and brightening, then it would seem the pwm code is good. Then replace the led with the motor and transistor etc, knowing that the signal to the transistor base is good.
Simulators do exist, but typically not IMO worth the effort because the Arduino is only capable of running small amounts of code, which limits the complexity of the logic to be debugged, and because the code is typically closely coupled to the hardware and without an accurate hardware emulator there is little point emulating just the microcontroller.
If you follow sensible development practice and keep different logical areas isolated from each other then you can test them individually. For example you would usually test the low level I/O mechanisms before writing the higher level logic that uses them.
I tend to write one small part of a program at a time, using just enough hardware to prove the concept. Sometimes that hardware is not extra, but is something like a series of Serial.print() statements showing me what's happening. I save every small program in a directory in case I need to use the technique in another program, and each one I save is fully tested, and written in such a way as to make it easy to grab the various parts of it for incorporation into other programs.
This technique is particularly valuable if the project has any complexity at all. Since I am very fond of defines, constants, functions, loops, and so on, I can usually grab the various parts, make a few changes (pins, value ranges, etc.), and just drop them in.
The example @JimboZA provided dealt with physical outputs.
Many Arduino projects will have physical inputs such as switches or voltage measurements. It's not possible to test the code without working inputs and the problem with simulated inputs (such as those generated by another program) is that they probably won't demonstrate real physical problems such as switch bouncing or noise in analog inputs which may have to be counteracted by suitable code.
I think this is fundamentally different from (say) code that is designed to deal with keyboard inputs which (a) could be accurately simulated and (b) can only be whatever the keyboard is capable of. And, usually, code is just interested in a few key-presses and can safely ignore everything else. (Of course an Arduino project may also have these types of input).
As usual, the approach depends on the situation. You can of course have code modules that are strictly code and can be unit-tested in a classical PC environment. If you rely on some hardware I/O, and that I/O can be simulated in a test harness, you can write your own "Arduino.h" that provides all the #defines and wrapper functions to return faux digitalRead() results, etc. However, the previous point made about predictable software inputs and somewhat chaotic hardware reactions is certainly worth keeping in mind.
In the end, a lot of debugging and troubleshooting is done in the hardware domain. Try and keep a pin free that you can use as a diag LED driver. (E.g., when you enter a function or a condition is met, you turn on the LED..) Similarly, Serial.print() is your friend, provided you're using the Arduino development board or have provided your own TTL-to-USB or TTL-to-RS232 interface.
Beyond that, you'll end up /* selectively enabling blocks of code */ to isolate particular scenarios and ensure they behave as you expect. On the hardware side, in approximate order of usefulness and increasing expense, you'll also want a good multimeter (pick one), logic analyzer (e.g., Saleae Logic 8), and oscilloscope (e.g. Rigol DS1102 and/or salvaged analog) if you intend to do anything more than make some LEDs blink.