When examining existing implementations, it is easy to see that adding tests to a program later on can be difficult — if the internal structure of the program is complex, you will not have the necessary access to data, methods, and the event handling system (when asynchronous interfaces are used).
Software Engineering – A Practitioner's Approach (Pressman) defines the following principles, which make software more testable:
Operability: The better the target software works, the easier it is to test.
By default, do not let code bloat, and remove bugs early.
Observability: What you see is what you test.
Missing source or documentation makes it hard to figure out how the target should be tested. Inaccessible attributes make it difficult or impossible to decide whether the results are correct or incorrect. Exceptions and error conditions and their outputs should be traceable so that it is possible to decide whether behavior was expected or unexpected.
Controllability: Keep implementations open so that tests can control the test target as easily as possible.
In practice, split long and complex methods to pieces and make them public or protected. The data that software uses should be also accessible and changeable from the tests.
Decomposability: Isolate problems by controlling the testing scope.
Software should be built from small pieces, which can be tested independently. In practice, one test case suite should test one class, and all dependencies should be replaceable with stubs or mock objects.
Simplicity: The less code there is, the less need there is for tests.
Functionality that is not used should not exist in the system, so remove dead code. Software architecture should be simple and modularized. Code is written as short as possible, is readable, and has any unnecessary control structures removed.
Stability: Fewer changes mean fewer disruptions to testing.
Changes to software need to be controlled and planned. The software is designed (and tested) to recover from failures. Pressman suggests minimizing changes to code and tests, whereas Agile developers prefer continuous code refactoring (change code, and test and remove unnecessary code).
Understandability: The more information testers have, the better the tests
Documentation in general is sufficient and correct. Component dependencies, the usage of external and internal components, are clear and understandable. Changes to the code base are communicated (use of versioning systems and visual diff viewers are useful for this).