Unit tests shouldn’t be testing types, even if your language isn’t typed. It should be testing logic and behavior. If there’s an if condition, it should be tested.
Yeah you’re right, tests should test logic. But static typing certainly helps reducing a lot of tests, which would be necessary in different untyped languages. Also you can sometimes encode your logic in types. Typing also helps reducing logic issues. But as previously said, it depends on what you’re doing. I’m prototyping/researching a lot, and tests often hinder progress for me. Maintaining a backend in production is a different story.
That is absolutely true as well. We’re porting a codebase to TypeScript and we were able to eliminate a bunch of test cases that were essentially testing type-correctness (e.g. can’t pass a boolean to a date processing library). But those were bad tests to begin with, because there was no good reason for those tests to exist to begin with (we were pretty exhaustive with the invalid type checking even when the intended types were obvious).
Strict typing helps eliminate useless tests. And Rust types go further than most languages, such as exhaustive match, types that can exclude zero, and the near-complete lack of a null value.
If you’re never going to publish the code, I agree, tests aren’t necessarily helpful. Then again, I find writing tests helps me understand my own code better, so I still do it when doing research tasks (e.g. we were testing the potential performance benefits of porting an expensive algorithm to Rust, so my tests helped me benchmark it), though my tests are a lot less exhaustive and tend to be more happy path integration tests instead of proper unit tests.
I find writing tests helps me understand my own code better, so I still do it when doing research tasks
Hmm interesting, I try to optimize readability of the actual code itself, so that when I read it again after some time, that I quickly get what this is about, if there’s a edge-case or something I thought about while coding, I’ll just add a TODO comment or something like that. I feel like reading tests is a “waste of time” for me most of the time (hard take I know ^^).
But all this obviously only applies for researching and fluid code (code that likely will be refactored/rewritten soon), when it’s solid code targeting production etc. I’ll add unit tests if friction/hassle is low, and integration/E2E tests for the overall thing. But as I said, I’m mostly in fluid/fast moving codebases that are more difficult to test (e.g. because it does gpu rendering or something like that).
When I jump into a new codebase, my first instinct is to look over the examples and the unit tests to get a feel for how things are intended to work.
When prototyping, I generally write a few “unit” tests as a form of example code to show basic usage. For example, if I’m writing a compiler for a new toy language, I’ll write some unit tests for each basic feature of the language. If I’m writing networking code (e.g. a game server), I’ll write some client and server tests that demonstrate valid and invalid packets. These generally fall somewhere between unit and integration tests, but I do them in a unit test style. When the project stabilizes, I’ll go through and rewrite those tests to be narrower in scope, broader in line coverage, and simpler, and keep a few as examples (maybe extract to the readme or something).
That’s my workflow, and I like knowing that at least part of it is tested. When I mess with stuff, I have a formal step to change the tests as a form of documenting the change I made, and I’ll usually leave extensive comments on the test to describe the relevance.
Code readability counts, but I don’t think it’s enough. The codebase I work on day to day is quite readable, but it’s very complex since there are hundreds of thousands of lines of code across over a dozen microservices, and there’s a lot of complexity at the boundaries. When I joined the project, I read through a lot of the tests, which was way more helpful to me than reading the code directly. The code describes “how,” but it doesn’t explain “what” or “why.” Tests get into “what” extensively, and “why” can be understood by the types of tests developers choose to write.
For example, if I’m writing a compiler for a new toy language
Ok, thinking about it (since I wrote a toy language not so long ago), this is probably a perfect example where unit tests make sense almost everywhere (even for prototyping, say parser).
I think it definitely depends what you’re doing, writing unit tests for prototype graphics (engine) code is no fun (and I see no real benefit).
Code readability counts, but I don’t think it’s enough.
I think it depends, For general architecture, E2E or integration tests definitely make sense, for finer-grained code, I think documentation (Rust doc) of the functions in question should be enough to understand what they do (including some examples how to use them, could be tests, often examples (similar as in std rust) in the rust doc are enough IMHO, and otherwise the code itself is the documentation (being able to read code fast is a valuable skill I guess). That obviously doesn’t apply for everything (think about highly theoretical computer science or math code), but yeah it depends…
Unit tests shouldn’t be testing types, even if your language isn’t typed. It should be testing logic and behavior. If there’s an if condition, it should be tested.
Yeah you’re right, tests should test logic. But static typing certainly helps reducing a lot of tests, which would be necessary in different untyped languages. Also you can sometimes encode your logic in types. Typing also helps reducing logic issues. But as previously said, it depends on what you’re doing. I’m prototyping/researching a lot, and tests often hinder progress for me. Maintaining a backend in production is a different story.
That is absolutely true as well. We’re porting a codebase to TypeScript and we were able to eliminate a bunch of test cases that were essentially testing type-correctness (e.g. can’t pass a boolean to a date processing library). But those were bad tests to begin with, because there was no good reason for those tests to exist to begin with (we were pretty exhaustive with the invalid type checking even when the intended types were obvious).
Strict typing helps eliminate useless tests. And Rust types go further than most languages, such as exhaustive match, types that can exclude zero, and the near-complete lack of a null value.
If you’re never going to publish the code, I agree, tests aren’t necessarily helpful. Then again, I find writing tests helps me understand my own code better, so I still do it when doing research tasks (e.g. we were testing the potential performance benefits of porting an expensive algorithm to Rust, so my tests helped me benchmark it), though my tests are a lot less exhaustive and tend to be more happy path integration tests instead of proper unit tests.
Hmm interesting, I try to optimize readability of the actual code itself, so that when I read it again after some time, that I quickly get what this is about, if there’s a edge-case or something I thought about while coding, I’ll just add a TODO comment or something like that. I feel like reading tests is a “waste of time” for me most of the time (hard take I know ^^).
But all this obviously only applies for researching and fluid code (code that likely will be refactored/rewritten soon), when it’s solid code targeting production etc. I’ll add unit tests if friction/hassle is low, and integration/E2E tests for the overall thing. But as I said, I’m mostly in fluid/fast moving codebases that are more difficult to test (e.g. because it does gpu rendering or something like that).
When I jump into a new codebase, my first instinct is to look over the examples and the unit tests to get a feel for how things are intended to work.
When prototyping, I generally write a few “unit” tests as a form of example code to show basic usage. For example, if I’m writing a compiler for a new toy language, I’ll write some unit tests for each basic feature of the language. If I’m writing networking code (e.g. a game server), I’ll write some client and server tests that demonstrate valid and invalid packets. These generally fall somewhere between unit and integration tests, but I do them in a unit test style. When the project stabilizes, I’ll go through and rewrite those tests to be narrower in scope, broader in line coverage, and simpler, and keep a few as examples (maybe extract to the readme or something).
That’s my workflow, and I like knowing that at least part of it is tested. When I mess with stuff, I have a formal step to change the tests as a form of documenting the change I made, and I’ll usually leave extensive comments on the test to describe the relevance.
Code readability counts, but I don’t think it’s enough. The codebase I work on day to day is quite readable, but it’s very complex since there are hundreds of thousands of lines of code across over a dozen microservices, and there’s a lot of complexity at the boundaries. When I joined the project, I read through a lot of the tests, which was way more helpful to me than reading the code directly. The code describes “how,” but it doesn’t explain “what” or “why.” Tests get into “what” extensively, and “why” can be understood by the types of tests developers choose to write.
Ok, thinking about it (since I wrote a toy language not so long ago), this is probably a perfect example where unit tests make sense almost everywhere (even for prototyping, say parser).
I think it definitely depends what you’re doing, writing unit tests for prototype graphics (engine) code is no fun (and I see no real benefit).
I think it depends, For general architecture, E2E or integration tests definitely make sense, for finer-grained code, I think documentation (Rust doc) of the functions in question should be enough to understand what they do (including some examples how to use them, could be tests, often examples (similar as in std rust) in the rust doc are enough IMHO, and otherwise the code itself is the documentation (being able to read code fast is a valuable skill I guess). That obviously doesn’t apply for everything (think about highly theoretical computer science or math code), but yeah it depends…
Yeah, I wouldn’t bother for graphics code either. For that, I want compilable examples, and that’s about it.
I do a lot of math and parsers, and that lends itself very well to unit tests.