Hacker News new | ask | show | jobs
by a_humean 873 days ago
I think the point in the article is coming from Python or languages with less expressive type systems there is an awful lot of overlap between compiling/type checking in rust and some of the tests that get written in those languages.
2 comments

It’s fundamentally different. Testing in a dynamic language will never ensure that your variable always holds a string and never a number. Compiling a statically-typed language however does exactly that. This difference is the same for all checks a compiler performs.

It’s important to understand that difference, because otherwise you may think that testing and the static analysis performed by a compiler are interchangeable. They are not.

> otherwise you may think that testing and the static analysis performed by a compiler are interchangeable. They are not

I agree with you, but I think this is the issue. A lot of people used to working in dynamic languages spend time writing up lots of tests for things that get covered “automatically” in statically typed languages, leading to the confusion you note. This, I’d argue, effects people who “convert” to liking the static language, and to those who think static lands cause only overhead.

> spend time writing up lots of tests for things that get covered “automatically” in statically typed languages

This is mostly down to poor testing culture in general. People end up writing a lot of meaningless unit tests when what you need is integration tests. However, there's very little tooling to help with integration tests (in all languages) as everything is focused on unit testing only.

With proper integration tests you will cover much of what the compiler gives you in statically-typed languages.

I don't think it makes sense to expect integration tests have 100% coverage, which is what you would need.
Nothing is really 100% coverage :)
Unless you are sqlite, they might actually credibly have 100% coverage.
They are not interchangeable but there is an overlap.

For normal code bases, this overlap is relatively small (do not need to validate input types, don't need to automatically convert strings to numbers, convert single items to lists, etc), but as you take advantage of the type system, the overlap can become slightly larger (wrap your types, create separate types for validated values, make impossible states impossible).

Granted, no matter how much of a genius you are with the type system, there will always be place for testing, I hope everybody understands that.

And if your type system is expressive ... you might still need to write tests to make sure that your types are correct and give you back the result you actually want :)

I've seen people build type-level DSLs. Sure they encode a lot of logic in types... but are you sure that logic is correct? :)

Testing and analysis aren't equivalent, but they can be similar enough in use to be interchangeable.

As an example the approach of 'type correctness through a type checker' and 'type-correctness through integration testing' is largely interchangeable in python. Even though they function very differently.

I've seen that take a lot lately, and it's odd because I remember the first big push for unit test popularization as being centered on Java and JUnit.

Java is obviously a statically typed, compiled language with memory safety. But back then everyone still understood the advantages of automated testing. Static typing frees you from a certain class of problems, but it's so far from the whole story, I can't believe it even needs to be said.

Java's type system is pretty limited when compared to Rust's, and Rust as a language is designed to eliminate certain classes of bugs, some of which can still happen in a successfully-compiled Java program. Certainly javac can help you prove correctness in some cases, but rustc can prove that in many more. (Also consider that, during the time period you're referring to, Java's type system was even more limited than it is now.)

Put another way, I feel more confident about my Rust code being correct after it finishes compiling than I do about my Java code when it finishes compiling. And I also feel more confident about my Java code being correct after it finishes compiling than I do about my Python code after... I save the file.

The compiler isn't going to be able to tell you that your fibonacci function actually outputs a sequence of fibonacci numbers. You still have to write tests to prove that just as much in Rust as you do in Java as you do in Python. The fact that Java/JUnit really pushed unit testing hard for the first time is likely just due to the sheer number of Java developers in the enterprise world at the time, and that Java was the big up-and-coming language ecosystem with a lot of money and support and mindshare behind it.

If when you hear "there's a lot of overlap between type checking and unit testing" your first thought is of Java's type system then I can definitely see why you'd be concerned.

Java's type system at the time you're referring to was extremely limited compared to Rust's ML-like type system or even TypeScript. It didn't need to be said back then that the type system wouldn't catch everything because Java's couldn't even catch null pointer exceptions, much less encode error states as algebraic data types or provide zero-overhead newtypes.

I agree that types can't catch everything, but a modern type system like Rust's can be used to prevent many more classes of bugs than just "oops, that was supposed to be an int not a float", and that's all OP is saying: if you're using your type system to its fullest you need many fewer tests than you do in a dynamic language.

> first big push for unit test popularization as being centered on Java and JUnit

Perl's and TAP came before the xUnit family. TAP quickly spread to many other languages because although we were testing in them, the idea of a standard test engine was new.

https://testanything.org

I think most people would count java as languages with a less expressive type system. For one it doesn't catch null exceptions, whereas languages like rust do.