5 ms·
You don't have to write as many tests when you use static typing, because your method contracts are solid. People often say this, and I don't get it. What JS t
by orangecat 6y ago
You don't have to write as many tests when you use static typing, because your method contracts are solid.
People often say this, and I don't get it. What JS tests are you writing that become unnecessary in TypeScript? I've used a fair amount of TypeScript and plain JS, and end up with similar amounts of tests for each. With JS, I almost never want to verify only that a value is of a specific type; I want to look at its contents, which means I'd need to write the same test in TypeScript.
- hnarn 6y ago> With JS, I almost never want to verify only that a value is of a specific type; I want to look at its contents We're pretty deep down in abstract discussions here and I have no idea what your code looks like or what it does, but I think it's helpful to point out that knowing the type means you already know the contents to some extent. The way I see it, checking if a value is what you expect is always good, but if you additionally get an error automatically because your number is now suddenly an e-mail address, or the function you wrote that expects phone numbers suddenly gets a name instead, in my mind that's "built in testing". It doesn't make other testing unnecessary but it sure raises the bar for cleanliness -- which is especially useful when you're working with other components that aren't JS where types do really matter (like databases, for example).
- ZenPsycho 6y agoI want to know what kind of code you're writing where a number can suddenly become an email address. I certainly hope you're not using type checking as a substitute for input sanitization.
- geoka9 6y ago> With JS, I almost never want to verify only that a value is of a specific type; I want to look at its contents, which means I'd need to write the same test in TypeScript. If such (refactored) method is being called from code that's not covered by tests, the bug may not be discovered until it blows up in production. With static typing it would be caught at compile time.
- orangecat 6y agoIf such (refactored) method is being called from code that's not covered by tests Well there's your problem :p I agree that if code has no tests, it's more likely to be correct if it's in TypeScript than JS. But I don't consider that an acceptable bar, so if I'm doing your code review I'm going to ask for tests in either case. And once you have tests that verify the behavior of the code, you're not getting much incremental benefit from type checking.
- yawaramin 6y agoUnit tests can only prove the presence of bugs; they can never prove the absence of bugs. Only a typechecker can do that (provided an appropriate implementation). E.g. here is an AVL tree that is verified balanced at the type level: https://sketch.sh/s/C7Afz38jtFRMTkkydeBgUA/ https://sketch.sh/s/C7Afz38jtFRMTkkydeBgUA/ No unit tests needed to show that it is balanced :-)
- stickfigure 6y agoLet's say you have a method that takes a thing: function doSomething(thing) { ... } How many different possible representations of a 'thing' do you have? A json object? A class object with behaviors? A database id? Some sort of natural key like a SKU? A URL? Is it a metric or imperial thing? You need integration-level tests around every method call to ensure that caller and callee agree what kind of 'thing' representation to use. Type systems can eliminate this class of bug entirely.
- pmontra 6y agoI never throw inputs of random types to my functions. Actually that could be a good idea, some fuzzying at the public API level could catch some bugs and attacks. But not at unit test level. If a function expects an integer argument I test it with integer values. That in Node, Ruby, Python and Elixir. I never saw anybody doing something different. Well, if we wanted to enforce types we could use any static typed language out there.
- jkachmar 6y agoThe inputs shouldn’t be “random”, but the idea is that you should provide an input distribution that’s roughly representative of your expected distribution of real-world values (+ some perturbation to find edge cases). As for frameworks in the languages you listed... Ruby and Python: https://github.com/HypothesisWorks/hypothesis https://github.com/HypothesisWorks/hypothesis Elixir and Erlang: https://github.com/proper-testing/proper https://github.com/proper-testing/proper Node and JS: https://jsverify.github.io/ https://jsverify.github.io/ As for real world use-cases, imagine you’re writing a program that accepts timestamps as input and has to implement branching, requirements-defined business logic based off of them. When you’re writing your unit tests you can use the requirements to select timestamps that are “known good” and “known bad”, but it’s hard to explore this state space on your own. Same thing goes for handling unexpected inputs to certain functions. You probably don’t want to check _every_ type of input for _every_ dynamic function, but it might make sense to make sure that certain “entry points” to your program fail in the expected manner when they get poorly typed input.
- Yoric 6y ago
- kingdomcome50 6y agoForget about the whole testing thing. In a way it’s a red herring. Let’s instead talk about refactoring (this is related to testing but not in the way it is being framed in your comment or many others). Idk about you, but when I’m sussing out the behaviors of a complex application it can take numerous iterations over different possible types and shapes of the data in order to come up with something both robust and performant. The single greatest benefit of languages that afford static analysis is that, when I change the shape of some piece of data where it is defined, my editor lights up like a Christmas tree and informs me of every place in my code that was just broken! This is extremely useful for being able to quickly iterate on features. I don’t have to remember all of the calls sites dependent on some API. It allows me to confidently make (sometimes large) changes to my domain and know for sure which pieces of code might also need to be refactored. I’ve worked in codebases where the confidence I’m describing above does not exist, and what happens is a lot of defensive programming and wasted effort (and time!) dancing around new implementation because no one wants to change anything. Changes tend to become “append only” (we can only add to the interface) because it’s hard to know what’s going to break if you actually change it. This can be okay most of the time, but sometimes new requirements... well... require new approaches. The above is related to “testing” insofar as I don’t need to test where pieces of code depend on one another in order to know when their contract breaks. But the benefit is not really about testing at all. It’s about work flow. It’s about velocity. And it’s about freedom.