Exactly. Verifying an algorithm is easy. Writing it is hard.
How would you verify that algorithm?
Forall A:
add(A, 0) == A
Forall A B C:
add(add(A,B), C) == add(A, add(B, C))
I think that’s sufficient. Rule 101 of useful testing is understanding the actual constraints you rely on.Maybe throw in add(A, B) == add(B, A) if you’re angling for a promotion…
add(a, b) => a == 1 && b == 1 ? 1 : a + b
Kinda disappointing your test would take so long to run and still catch a simple error.I think GP's test would probably be implemented with property testing (or even better, symbolic execution). It wouldn't necessarily take super long to run.
assertEquals(0, add(0,0))
assertEquals(1, add(0,1))
assertEquals(2, add(1,1))
assertEquals(8, add(3,5))
assertEquals(-3, add(2,-5))
PS: This kind of proves why a test is a lot less useful than the algorithm. Try figuring out how to implement "add" from the above test. it('should work', () => {
inputOutputPairs.forEach(([input, output]) => {
expect(testedFunction(input)).equals(output);
})
})
Tests are way more useful when they are written with the intent of acting as documentation. If I had to give this test a grade (from F- to A+), it would get a D+. It catches regressions, yet does an absolutely horrid job of explaining the intent behind testedFunction. cases = [
{ in: 1, arg: 2, expected: 3, message: "base case" },
{ in: 2, arg: -2, expected: 0, message: "negative numbers" },
...
]
for ( c in cases ) {
actual = obj.do( c.in, c.arg )
assert( actual == c.expected, c.message )
}
see: http://fit.c2.com/ ... table-based testing, "cases" and "drivers"