Learn Go by writing tests – Hello, world (2018)
github.com
github.com
https://github.com/quii/learn-go-with-tests
Or this if you really want the Hello World example:
https://github.com/quii/learn-go-with-tests/blob/main/hello-...
This is at least a great reason why this 'test name with spaces' pattern isn't found in the standard library: https://github.com/golang/go/search?q=t.Run
Edit: Since this seems to be an unpopular opinion why stop here, haha: The more one stresses tests the more he/she signals that he must have missed years of advancements in software engineering. If you do this with peers, ok, but publicly? Even Ruby added types, not the kind of types we hoped for but still, it's a strong signal. If your lang has a mature type system you don't need half of your tests anymore and might not be into such write-ups. Praising and writing gazillions of tests don't make you look smarter, very much the opposite.
Where did I say that? I just meant, and my apologies for not being clear, focus should be types, ofc you still need tests. But not as many as a decade ago and more important, people must drop this dogma that tests are the key to everything. There are not and the more tests a codebase has the worse its quality and maintainability.
> I still practiced TDD
You can do this ofc but my experience differs: Once you have an excellent type system, both in terms of language features and tool chain eg editor, you can literally code for days without running even the compiler once. This is pure flow and very much the opposite of TDD. But the entry barrier of is much higher than TDD. Don't get me wrong, you still need tests but TDD?? IDK, this feels like trial-and-error-coding from 2010. I mean if we still used all Ruby, yes tests and TDD everywhere but the environment has changed.
I'm fully sold that types are important, personally I would object to starting any mid- to large-scale project in a dynamically typed language, but this doesn't ring true at all.
When you're writing and refactoring code that uses complex logic, you aren't necessarily able to encode that logic in the type system. Carefully written tests allow you to confidently edit the code without worrying that you might have broken something in the process.
If anything, strong type systems allow you to change the way you write and structure tests (more towards property-based testing as opposed to dumb test cases), but I wouldn't advocate completely doing away with them.
didn't say this
> Carefully written tests allow you to confidently edit the code without worrying that you might have broken something in the process.
yes true but again you get this with typed code without any tests for 80% of the code as well, look, it's about the quantity and what you are going to test. with types you need way less unit tests (some like ben awad say none!) but still integration tests. still doing tests like crazy and like it's 2010 defocusses your devs and makes refactoring much more tedious, change a small thing and rewrite twenty unnecessary tests from a too ambitious test warrior who didn't understand types. this creates a notion where codebases get stale and untouched for years. nobody likes to refactor test code bc it's an unattached piece of code which complicates things more than it helps, it rarely feels like a true spec but rather like some random code and the next one wonders why his predecessor wrote this test at all. this is so the past idk why people are worshipping this.
Write tests where types don't help anymore (integration tests!) and things are crucial, otherwise focus on the core logic. I have rather devs who write excellent typed code with just very few integration tests than somebody who drives nuts and goes down the full rabbit hole writing 10x more test code nobody asked for than actual code paired with such blog posts like from OP on top, they've missed the boat.
I'm fully sold on this as well, but:
> it's about the quantity and what you are going to test
I'd say it's more about how you're going to test. What is covered by the type system should be handled by the type system, that's an absolute no brainer, using tests, or even worse, comments or conventions as opposed to types is just objectively wrong.
Because you can now be confident that trivial mistakes will be caught by the compiler, you can have actually meaningful tests, like "this property is satisfied", as opposed to "this object has this field set to this string".
So I wouldn't say "write less tests", I'd say "since types free you from the burden of testing stupid things, write better tests".
It's a misconception that developers who use dynamically typed programming languages write tests that perform tasks of a static type system. They do not write tests like this:
assertException(() => upcase(12));
assertException(() => upcase(true));
assertException(() => upcase(null));
assertException(() => upcase(new Object()));
...
They write tests like assertEquals("TEST", upcase("test"));
assertEquals("HELLO, WORLD", upcase("hElLo, wOrLd"));
assertEquals("BLA123", upcase("bla123"));Those tests that you list later are "happy path" tests. We want to know that that works, but we can't rely on only that sort of test especially if the type system doesn't work with us to avoid incorrect inputs to the function.
Honestly if a sad path causes a typing error in elixir it's not the worst thing. Sentry will catch it, only the user thread crashes, and you go patch it later.
A type is literally a description of a set of valid values. So when you say you test with bad values, then the answer is: you could use types and would not need these tests anymore.
However, the more interesting question is: is your typesystem capable of expressing your type and, if so, is it worth the effort and implications to do so.
But on a more theoretical level, OP is right: you _can_ save the tests with, given a powerful enough typesystem.
No. These are not internal contracts, these are contracts with user input. In a statically typed language, You are still advised to write tests that your marshalling technique provides the expected error (and downstream effects) that you plan for, if say the user inputs the string "1.", For a string that should be marshalled as an integer.
> A type is literally a description of a set of valid values.
That is generally not the case. There are, for example cases where certain floating points are invalid inputs (either outside of the domain of the function or a singular points), and I don't know of a mainstream PL that lets you define subsets of the reals in their type system.
In go, or c, c++, or rust, you could have a situation where a subset of integers are valid values (perhaps you need "positive, nonzero integers", because you are going to do an integer divide at some point) and that is not an expressible set of value in that type system. Ironically, that is a scenario that IS typable in Elixir and erlang, which are dynamically typed languages.
Yes and no. The trick is building a type system simultaneously strong enough to encode the properties you want, and weak enough that it's statically decidable.
There will always be properties you can't encode in a (useful) type system.
It's a fine line to walk, really. There's an argument to be made that most of the times we don't actually need Turing-completeness, and we'd better off using only types to encode computations, but OTOH I don't really want to think about what coinductive types I must define to solve a problem that could be solved with five lines of javascript instead.
If you define useful by "we know that the compiler will finish in finite time" then I agree. And that's indeed a good point! In practice, there will always be runtime tests, at least for how long any of us and our children will live. :)
Re your second code block: This can be typed with literal types, no need for tests at compile time.
Sure, they are validated at compile time because they are propositions as types, but in the end they are still basically test cases: expected output for a given input, and the compiler is the test runner.
I don't know how to encode the full "Game of Life" property as dependent types, I am still an Agda newbie.
What would you give as examples for language features and toolchains that enable the workflow you are suggesting?
TS has the best and most responsive editor support (tsserver). I know that Rust's is much slower but IDK much more than that.
Re ecosystem and build system: TS' build system is not trivial but it's very flexible and has a bigger ecosystem.
It is great to see though that even frontend-mainstream languages like Typescript start to get proper support for type-systems (especially considering that they had to do it on top of Javascript).
Very excited to see where we go with languages and I agree with you that TDD should now by default mean "type driven development". :)