Gary Bernhardt - TypeScript and Testing
Thing that you said about the test, we verifying the behavior system is totally true if you number one have well, if you have perfect coverage to the system. And that doesn't mean just line wise coverage because executing every line isn't sufficient. It doesn't mean even path coverage path coverage means like let's say you have a function with two with a conditional with two sides and an els, and then after that on related to the second conditional with two sides, you have four paths to that function because you can take either side of the first conditional on either side of the second. So. Even covering all the paths isn't sufficient because the data might vary in weird ways that 'cause you might, for example, you never thought to test for when some arrays empty and you're indexing until you're getting an undefined out your test never tested that. So the thing about. Let's throw away the integer versus strength and just think about like you have a function that takes a supposedly takes a string. and. You didn't think about what happens if it's undefined. Well, maybe you never pass undefined that function, but maybe you pass something that was indexed out of an array to a, and if that array was emptier, didn't contain that index, you're going to get an undefined and this is like at least in dynamic languages. This is one of the most common and annoying sources of bugs is whatever kind of knowles your language has and they show up. Yeah Yeah. Exactly. And these languages tend to have very lax with with somewhat an exception of Python. They have very lax handling of knowles. So like indexing ray with an unexpected index gives you all in undefined, her nail or whatever. It's called same thing with Hashes objects. So. One of the big benefits of a static type system. As you can say, this function takes a string and under no conditions will allow you to pass an undefined into their. except. Took mentioned, the type script was designed under severe constraints. Actually, type script does have this problem because of the type of indexing array is the type of the array. It is not union with undefined. So. This is not the greatest example, but there are lots of sort of other situations like this objects are a better example. Cause type stripped will prevent you from. Accessing a property that doesn't exist on an object and getting an unexpected undefined, and then passing that down into a function that never expected the undefined, all of that kind of weird. Stuff. This kind of weird bugs that can only occur due to run time. Data. A lot of them go away, not all of them and you know the language determines how many if you're programming Idris like really, you get pretty close to all them going away if you programming in type script like. It probably gets you eighty percent of the way towards preventing those kinds of things. But. This stuff comes up constantly with. A. So that was all. That's all of that was supposing perfect test coverage. You still have these problems that are based on run time data. They're not based on the structure of the code you wrote, would that mean that you don't actually have perfect test coverage though like that is the implication. So. I. Don't want to write I, don't want to one. Hundred Percent Path Coverage, I? Don't even want achieve one hundred percent line coverage. So an execute programs code base. The ratio of test code to should've written the actual number down, but it's something like maybe four to one production code test code is. That is that feels like a lot more production code to TESCO than what I remember seeing like the destroyer software screen casts and stuff. Like Ruby and rails stuff. Yeah, and it's a lot. It's a lot more production to test than in the destroy all software code base as well because in Ruby and like I have to. Constantly. Protect myself from from mistakes. and. With type script and play. I can strategically test. Things at just the right place. So like EXCO program has this core bit of code that handles year kind of progression through a course and the lessons and so on. That's really critical and quite complex code. All of that is like just paranoid testing around because if any of that ever goes wrong, it's going to get baked into the database and it's you know it's GonNa be there forever. But for example, our reactor components. Strictly speaking we have zero tests around the entire react. And that's there are a few points that are actually tested, but they're kind of factored out like reducer, kind of stuff like the actual. Component testing. But just like there's some areas of logic that I'd extract, it emerged test for because it was easier to write the code if I had tests while I was writing it sorta thing. Yep, exactly. and that's not to say that the front is never tested because we have some Cyprus tests drive a browser so. Are, tests actually step through every single lesson in the system just to make sure that nothing blows up, but that's more of a just a giant smoke test. So. And it requires no incremental work when I write new content or new components, just kind of works for free. So. By, having type script in place, not having to worry about all the wiring of components I can focus on. Very, complex tight fine grain tests for the core logic. Of the system and I can have this nice high level test runs through and make sure nothing is totally broken, but I don't have to write a whole lot of just really boring like this component. When it this, it renders you know with this class or whatever I. Don't do any of that stuff and. I. Think it's worked really well, I have no regrets about about switching type script for that purpose because the overhead is quite small when programming but the test over the saved as massive, I mean, we're talking about. I wrote a blog post with numbers in it, but I think it was something like the system would be two point, five times as many lines of code. If I had done standard, what was it two to one test to code ratio?