My view is that testing and strong typing both help with lowering the cost of change, but they do so in different ways, and are therefore not a substitute of each other.
Strong typing pins down the basic interface of each callable function, defining what is expected as input and output. If a change breaks such expectation, the type system tells us. Additionally, when used properly, types also provide documentation, specifying explicitly what a function takes as input and what it returns as output.
Good tests pin down invariant properties of the system behavior, so that if a change breaks such invariants, the test tells us. Ideally, a test would assert an invariant property without restricting the specific implementation, so that one could change the implementation and still pass the tests, if the invariant is respected.
Here’s a toy example to illustrate what I mean. Suppose I want to implement a sort of key-value store, with two functions: set(key, value) that sets a key to a value, and get(key), that gets the value associated to a certain key, if any.
The type system can specify what keys and values are supposed to be, and even what happens if I try to get a key that is not associated to any value.
On the other hand, types won’t help in ensuring that if I set a certain key K to a certain value V, and then get the key K, I get exactly the value V and not another one of the same type. For that, we need a test like “set K to V. Then get K, and assert that the result is V”.
Such test asserts an invariant that the system should respect, without specifying what the implementation should be. Under the hood the system could be implemented as a hash map, as a database call, etc. without failing the test. This is easier said than done in more realistic cases, but that’s the aspiration.
Sometimes, a bug is discovered, and a test can be added to ensure that a similar bug cannot be re-introduced with future changes. That’s protecting against regressions, and again, ideally it would pin down some invariant that should hold for some corner case, rather than a specific implementation.
I don’t think that tests (or types) help much with ensuring correctness. What really matters for me is to lower the cost of change, and both tests and a good type system help, in complementary ways.






















