The crucial difference between tests and implementations is that implementations have to satisfy all of the requirements at once, while tests can check a single thing at a time. As a simple example, if we need to test a 'sort' function for lists, we might write a whole bunch of tests like this:
"Sorting" should "preserve length" in {
forAll() { (l: List[Int]) => assert(sort(l).length == l.length) }
}
"Sorting" should "be idempotent" in {
forAll() { (l: List[Int]) => assert(sort(l) == sort(sort(l))) }
"Sorting" should "put elements in order" in {
forAll() { (l: List[Int]) =>
whenever(l.nonEmpty) {
sort(l).foldLeft((true, l.min))({
case ((result, max), elem) => (result && max <= elem, elem)
}) match {
case (result, max) => assert(result && max == l.max)
}
}
}
}
"Sorting" should "not change elements" in {
forAll() { (l: List[Int) => {
val sorted = sort(l)
assert(l.all(elem => sorted.contains(elem)))
assert(sorted.all(elem => l.contains(elem)))
}
}
Each of these tests can focus on one aspect of sorting and ignore everything else. Each on its own is not enough to give us confidence in the 'sort' function (e.g. the identity function would pass the idempotence test, the same-length test and the same-elements test; returning an empty list would pass the elements-in-order test; etc.), however together they are pretty good.
In contrast, we can't implement the 'sort' function in such a piecemeal way. It needs to take every requirement into account, in case a step which satisfies one requirement invalidates the others. That's also why writing a whole new implementation to test against is best seen as a last resort.