Languages with powerful type systems do allow the programmer to ensure consistency without duplicating code, to a degree. And I suspect that it does reduce the testing burden substantially when used correctly.
There will always be some need for all of the following: static checking (e.g. compiler checking the types), tests, and code review. There's a simple economic reason for this: if you omit any one of those strategies, then the cheapest way to find the next bug will almost certainly be the one that you omitted.
You're right that tests are redundant (you could say the same thing about type annotations, perhaps), but redundancy is underrated. Redundancy aids readability, and it also helps catch mistakes when there's an inconsistency. If you put code and tests near each other, it might be helpful to think of it like: "<code>. In other words, <test>." Similar things could be said for type annotations, declarations, and constructors; or code comments.
I suppose testing could be understood as a special error-correcting code for a particular noisy information processor -- humans writing software.
But then one must begin to wonder, are they doing that job very well? Testing does not seem to be so carefully designed as Hamming codes or others . . .
It's not as much about clarity of communication as it is clarity of thought.
Also, a program is not a single message being sent to the computer. A program is revised over time, and testing helps ensure the integrity of the program through revision.
> ensure the integrity of the program through revision
Yes, that is what I am thinking: each step is like sending the signal through a noisy channel (that also does some transforming -- it is not an exact analogy). But testing doesn't seem to be carefully designed to address the particular kinds of 'noise'/mistakes that humans make.
There will always be some need for all of the following: static checking (e.g. compiler checking the types), tests, and code review. There's a simple economic reason for this: if you omit any one of those strategies, then the cheapest way to find the next bug will almost certainly be the one that you omitted.
You're right that tests are redundant (you could say the same thing about type annotations, perhaps), but redundancy is underrated. Redundancy aids readability, and it also helps catch mistakes when there's an inconsistency. If you put code and tests near each other, it might be helpful to think of it like: "<code>. In other words, <test>." Similar things could be said for type annotations, declarations, and constructors; or code comments.