> Static typing gives a good baseline of correctness.
Not really. Lets say you have an `add(a, b)` function. Static typing can guarantee that the function returns a number, but not that it returns the correct number. So you need unit tests anyway.
A unit test `assert_equal(4, add(2, 2))` actually tells you something about the correctness about the function, and it implicitly also verifies that it returns a number. So static typing does not save you anything.
Static typing does have advantages, for example for documentation and for automated refactoring. But it doesn't replace any unit tests.
Sure, in this trivial example there may not be clear benefit, but with more complicated code, operations on complicated structures, with potential state changes, etc. there are many benefits. Even in your trivial example you don't cover the correctness or correct usage of your function - since it's dynamically typed it could be called with non-integer arguments - what's the expected behaviour in those cases?
Static typing certainly have many benefits, just as it has a number of drawbacks. But I'm not making a general argument about static versus dynamic typing, I'm just rejecting the specific argument that static typing saves you from writing any unit tests.
Nobody made the argument that static typing removes the need for unit tests. It does check for many conditions that would have to be unit tested otherwise, such as calling functions with the wrong types.
I'm saying static type checking does not replace any unit tests, because you wouldn't write tests which only checks for "the wrong types" in the first place. You should check for valid behavior - i.e. is the output correct, not just of the expected type. Types are just an implementation detail. But if the tests verify that the output is correct, then it implicitly follows that the types are correct, so you get type checking for free.
No, as I've pointed out - even in your trivial example you're not testing for all correct behaviour. What's the correct behaviour when the function is called with invalid argument types?
> But if the tests verify that the output is correct, then it implicitly follows that the types are correct, so you get type checking for free.
Your unit test checks the correct behaviour for one out of infinite possible inputs. You don't 'get type checking for free' , what you get is no type checking at all.
This is true - unit tests generally does not test all possible inputs. They test some representative examples and edge cases. In most cases there are infinitely many possible inputs, and you get diminishing returns the more cases you test.
If I understand you correctly, you a suggesting unit tests in a dynamic language should test all possible invalid inputs. Since statically typed languages eliminate a subset of invalid inputs (because they won't compile), statically typed languages saves you from writing some of these tests.
My argument is that you wouldn't write such tests in the first place since they provide little to no value.
If function `foo()` calls function `bar()` you want to ensure that foo passes the correct arguments. You don't do that by checking that bar() rejects all possible invalid types. You do it by checking that foo() actually works and provide the correct result. The types involved are an implementation detail.
> You do it by checking that foo() actually works and provide the correct result.
But you're testing a tiny subset of possible scenarios of behaviour of the function. If you can anticipate all possible input types and value ranges, maybe unit tests are enough, but that's not realistic in a dynamic language/program where complex values are non-deterministically generated at runtime - based on user input, db, file, etc.
In any statically typed language I know, it is possible to create invalid values which looks correct to the type checker. E.g. if a function takes an interface as parameter type, you can create a dummy implementation which throw exceptions for any operation. So basically you have the same problem as you describe for dynamic languages.
If you are talking about untrusted external input then yes, you need to carefully validate and reject invalid input. If you receive JSON or a CSV file or whatever, there are an infinite number of valid inputs and an infinite number of invalid inputs. But this is the same for statically typed languages, and the type-checker will not help you, since a valid JSON string and an invalid JSON string look exactly same to the type checker.
> E.g. if a function takes an interface as parameter type, you can create a dummy implementation which throw exceptions for any operation. So basically you have the same problem as you describe for dynamic languages
That's not at all the same problem, because here you're intentionally writing code that throws exceptions for some reason? Also, many languages, such as Java will not allow you to throw a checked exception that's not declared on the interface method - such issues will be statically checked and caught - thanks for proving my point ;)
> of valid inputs and an infinite number of invalid inputs. But this is the same for statically typed languages, and the type-checker will not help you, since a valid JSON string and an invalid JSON string look exactly same to the type checker.
Not at all, in a statically typed language the JSON would typically be deserialized into a structured type with all the benefits of type checking, validation and usage of the resulting values. Of course it's possible to just use a JSON string directly, but that's not idiomatic and generally not the way it's done in quality codebases.
This kind of thing is really what contract-based programming is meant for; it shouldn't properly be a test, except that we have to resort to that for the lack of language support for DbC.
Not really. Lets say you have an `add(a, b)` function. Static typing can guarantee that the function returns a number, but not that it returns the correct number. So you need unit tests anyway.
A unit test `assert_equal(4, add(2, 2))` actually tells you something about the correctness about the function, and it implicitly also verifies that it returns a number. So static typing does not save you anything.
Static typing does have advantages, for example for documentation and for automated refactoring. But it doesn't replace any unit tests.