3 ms·
> Once you've tested the complex things that (almost) no language has a type system able to express, you also have tested null checks, function parameter types,
by bccdee 1y ago
> Once you've tested the complex things that (almost) no language has a type system able to express, you also have tested null checks, function parameter types, etc. by virtue of you needing to visit those situations in order to test the complex logic.
That's not true. At no point in testing `fn add(a: i32, b: i32) -> i32` am I going to call `add("a", "b")` or `add(2, None)`. Rust even won't permit me to try. In a language with a more permissive type system, I would have to add additional tests to check cases where parameters are null or of the wrong type.
- 9rx 1y ago> At no point in testing `fn add(a: i32, b: i32) -> i32` am I going to call `add("a", "b")` or `add(2, None)`. It seems you either don't understand the topic of discussion or don't understand testing (see previous comment). If the user of your function calls it in undocumented ways, that's their problem, not yours. That is for their tests to reason with. Passing the wrong types is only your problem for the functions you call. Continuing with your example, consider that you accidentally wrote (where the compiler doesn't apply type checking): fn double(a: i32) -> i32 { add(a, None) // Should have been add(a, a) } How do you think you are going to miss that in your tests, exactly?
- seanwilson 1y agoReal functions are tens of lines or more long, have complex inputs, multiple branches, and call other complex functions, so tests that try a few inputs and are only checking for a few behaviours aren't going to catch everything. If it's practical to get a static type system to exhaustively check a property for you (like null checks), it's reckless in my opinion to rely on a test suite for that. > If the user of your function calls it in undocumented ways, that's their problem, not yours. Sounds reckless to me as well because you should assume functions have bugs and will also be passed bad inputs. If a bug makes a function return a bad output, and that gets passed to another function in a way that gives "undocumented" behaviour, I'd much prefer to code to fail or not compile at all, because when this gets missed in tests it'll eventually trigger on production. I view it like the Swiss cheese model (mentioned elsewhere), where you try to catch bugs at a type checking layer, a test suite layer, code review, manual QA, runtime monitoring etc. and you should assume flaws at all layers. I see no good reason to skip the type checking layer.
- bccdee 1y agoWhat your tests will miss is the invalid data that was constructed in one corner of the codebase // apply special discount newPaymentInfo = {value: oldPaymentInfo.value / 2} newPaymentInfo[tax] = applyRegionalTax(newPaymentInfo.value) return newPaymentInfo which only gets parsed by another corner of the codebase at runtime. // apply tax to tips only in some regions if (taxableTips) { paymentInfo.tip += applyRegionalTax(paymentInfo.value) // ERROR: tip is undefined (instead of zero) } You can't validate everything all the time, and if you try, it's easy for that validation to fall out of sync with the actual demands of the underlying logic. Errors like this crop up easily while refactoring. That's why one of the touted benefits of Rust's type system is "fearless refactoring."