While reading books and internet articles, it seems there is an agreement that writing tests is not about finding bugs. If not that, then what is the purpose of testing?

Writing tests allows you to look at a unit of software from the perspective of a client, not an author. A test is the first user of a unit of your code.

Test Loop

Writing tests is not a linear process. Rather, it is a feedback loop that consists of writing a test, allowing it to fail, then writing code that passes the test.

The cycle should be short and fast— a matter of minutes. You should constantly switch between writing tests and making them pass.

You should test early, test often and automate testing

Unit Testing

Unit testing embraces component-driven design. It resembles the ideology of integrated circuits, where each component works independently and reliably thanks to rigorous testing and well-defined interfaces.

A unit test sets up the environment, runs the routines, and compares results against known values or previous results (regression testing).

Testing Contracts and Interconnected Modules

Testing can assure that contracts between modules are honored. If there is a module “A” that relies on “B” and “C,” testing can ensure that if “B” and “C” units pass their tests but “A” does not, then the problem lies in module “A”.

Testing in this way reduces debugging time and minimizes the chances of “time bombs.”

Property Testing And Regression Tests

This kind of testing randomly generates a large number of test cases and compares them against expected results. Every time the test runs, a different set of test cases is generated. If the test fails for certain values, it means a new regression test is required—a test that runs the routine with the specific values that caused the property test to fail.

Testing Change Your Mindset

Testing forces you to understand what you are building before you start typing.

Testing first allows you to understand the scope of the work, reflect on boundary conditions, edge cases, and possible errors that need to be handled.

Decoupling and Modularity

Testing promotes modularity by enabling you to decouple components. For example, a function that interacts with a database should abstract the connection, allowing it to work with test, staging, or production databases based on context.

Testable code reduces coupling, as tightly coupled units are harder to test and require complex setups. Decoupling simplifies testing and leads to cleaner, more reliable code.


Find out more in the book “The Pragmatic Programmer” by David Thomas and Andrew Hunt.