Testing and Design

Last updated on 2026-07-16 | Edit this page

Estimated time: 20 minutes

The “Holy Trinity” of Sustainable Software


As your scientific projects grow in size and complexity, you will inevitably hit scaling friction. You might find that writing new features breaks old ones, or that collaborating on Git leads to endless, painful merge conflicts.

These problems are rarely just “version control” or “testing” issues. They are design issues. Modern, sustainable software engineering relies on a tightly integrated feedback loop often called the Holy Trinity:

  1. Modular Design: Breaking your code into small, decoupled, single-responsibility components.
  2. Unit Testing: Rapidly asserting the behavior of those isolated units.
  3. Version Control (Git Workflow): Developing on clean, un-entangled feature branches.

If your code is modular, writing unit tests is trivial. If your code is covered by fast, reliable unit tests, you can write code on a Git branch and merge it with 100% confidence.

If you find yourself struggling to write a clean unit test—such as needing to mock out 10 different database calls, file paths, and network APIs just to test a simple mathematical calculation—your testing framework is trying to tell you something: your design is tightly coupled.


Addressing the Big Objection: “This Takes Too Much Time!”


When first encountering Test-Driven Development and property-based testing, a very common and understandable objection is:

“This takes way too much time! I am writing almost more test code than actual implementation code. Is this really how professional computational work is done?”

There are three ways to look at this objection:

1. Conway’s Law of Prototyping

Computer scientist Melvin Conway formulated an adage (often called Conway’s Second Law or Conway’s Law of Redoing) that captures the reality of software schedules:

“There’s never enough time to do something right, but there’s always enough time to do it over.” — Melvin Conway

When we skip writing tests because we are “in a hurry,” we are not actually saving time. We are simply taking out a high-interest loan against our future schedule. * Writing code without tests feels fast initially because we defer the cost of debugging to the future. * However, finding and fixing a bug in a complex, finished system takes exponentially more hours than preventing that bug during incremental design.

2. Industry Standards: Test-to-Code Ratios

Writing more test code than production code is not a sign of poor progress; it is the industry standard for highly reliable systems:

  • SQLite: The most widely deployed database engine in the world has roughly 150,000 lines of production C code, but contains over 90 million lines of test code. This is a test-to-code ratio of roughly 600 to 1.
  • NASA/JPL Flight Software: Mission-critical deep space software projects routinely feature test suites, simulators, and validation systems that outsize the actual flight code by 10 to 1. When a patch cannot be easily deployed, testing is the primary mechanism of survival.

3. Your Real Job

Your job as a researcher or software engineer is not to write code. With modern large language models, code has become a cheap commodity.

Your actual job is to deliver a reliable piece of scientific software that you, your collaborators, and the broader scientific community can trust. If your simulation code produces an exciting, breakthrough result but contains a silent mathematical error, your scientific discovery is built on sand.


The Great Water Simulation War (A Cautionary Tale)


To understand the real-world scientific cost of untested code, consider the famous academic battle over supercooled water that took place between roughly 2011 and 2017.

For decades, physicists debated whether supercooled liquid water could undergo a “liquid-liquid phase transition,” splitting into two distinct phases (low-density and high-density liquid).

  • The Discovery: A research group at Princeton ran massive molecular dynamics simulations and published results showing clear thermodynamic evidence that this second phase transition existed.
  • The Challenger: A rival group at UC Berkeley published papers flatly contradicting Princeton’s work. They claimed the second liquid phase was a complete illusion—an artifact of slow crystallization—and released their own custom simulation code to prove it.
  • The War: For seven years, these two elite groups fought bitterly at conferences and in journals. Careers of junior researchers were stalled, and massive supercomputer allocations were consumed as both sides tried to resolve why their simulations did not agree.
  • The Bug: In 2017, independent researchers finally performed a rigorous audit of the Berkeley group’s custom simulation code. They discovered a subtle, hidden coding bug in the Hybrid Monte Carlo algorithm. The code successfully swapped the velocities of the water molecules, but failed to correctly account for the rigid-body rotational degrees of freedom.

Essentially, the bug left the translational motion of the molecules “hot” while the rotational motion was “cold.” This thermodynamic imbalance completely biased the simulation, erasing the second liquid phase and creating the illusion that Princeton’s discovery was wrong.

When the bug was corrected, the Berkeley code reproduced Princeton’s results perfectly. Seven years of scientific progress and computational resources were wasted because of a single, untested mathematical error in an academic script.


Terminology: Unit vs. Integration Testing


As you navigate testing literature, you will see two primary terms used to describe different types of tests:

Test Type Scope Environment Execution Speed
Unit Test A single isolated “unit” of logic (e.g., one mathematical function or class). In-memory only. No disk, database, or network calls. Milliseconds (run thousands per second).
Integration Test Multiple modules interacting, or interactions with external systems. Hits local filesystems, test databases, or APIs. Seconds to Minutes.

The Testing Spectrum

In textbook theory, there is a sharp line between these two categories. In practice, the boundary is a spectrum of degree and context.

If your core overlap calculation calls a simple coordinate converter helper function, it is technically involving two units, but it is still fundamentally a unit test if it is fast, deterministic, and isolated in memory.

::: rationale ## The Takeaway Do not waste valuable development time arguing over pure definitions on your team.

Instead, focus on isolating non-deterministic, slow, or stateful operations (like reading files, querying databases, or making API calls) from your pure logic and algorithms (like coordinate math, simulations, and data parsing). :::