Tests vs Types
"We don't need complex, stupid, cumbersome types and compilation to prevent bugs in production, types can't prevent all bugs anyways. Just write more tests!" -- Tess
"Yes we do! We wouldn't need to write so many complex mocks for tests that take forever to run in our CI / CD pipeline if we used a REAL language that is statically typed. " -- Tyler
As you can see from the conversation, or argument above, Tess prefers Tests and Tyler prefers types; who is correct?
Two sides; Same Coin
I myself believed one side of this false dichotomy at one point. They aren’t mutually exclusive. Now PLEASE I am not getting into the weed of TDD, type systems etc in this post. I’m merely showing that Types cannot replace test, generally, and tests cannot replace types, generally.
When I say test
I mean Black box unit testing.
The Proof is in the Code
So let’s write a Jest test for a simple sum
function.
const sum = require("./sum");
test("adds 1 + 2 to equal 3", () => {
expect(sum(1, 2)).toBe(3);
});
Here is the implementation of the sum
function.
function sum(a, b) {
return a + b;
}
module.exports = sum;
So what if we write a new function to calculate the bill total at a restaurant.
function billTotal(subtotal, tax, tip) {
return sum(subtotal, tax, tip);
}
billTotal(70.2, -0, "5%");
Notice that we are using the function incorrectly. Where does our test prevent that? It doesn’t. Sure a unit test could handle that case BUT our billTotal
function still wouldn’t work. Yes, we could write yet another test
to test billTotal BUT tests should verify implementation, NOT usage. This also make it the user’s responisiblity to check that. A type system would catch this immediately, no test required.
With Types
function sum(a: number, b: number): number {
return a + b;
}
Advanced Types
Types can do A LOT more than that. Rust
uses Types to create safe and fast code. Prevent race conditions. In fact, great
language like OCaml
have types but you don’t write them, they’re inferred.
Types can replace logic checks. Error handling.
type TempResult = Tempdata | Error;
function Foo(request: Request): ResultError {}
if (Foo() === Error) {
// handle error
} else {
bar();
}
function bar(temp: Tempdata): bool {}