Hacker News new | ask | show | jobs
by qsort 1922 days ago
> There are not and the more tests a codebase has the worse its quality and maintainability.

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.

1 comments

> 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.

> doing tests like crazy and like it's 2010 defocusses your devs and makes refactoring much more tedious

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".

> with types you need way less unit 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"));
Having used both dynamic and statically typed languages rather extensively, I always end up recreating some subset of the type system in the test suite for dynamically typed languages. Often to at least test that the functions correctly handle erroneous input. Taking your example, I would definitely have at least one of those assertException kind of tests, and more of them (but automatically generated) if using a property-based test system where I could say something like: any type but string should result in an exception/error result. Now, this wouldn't be exhaustive (again, sans automation), but I'd have at least one test covering this.

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.

I currently use a dynamically typed pl in my professional capacity (statically typed pl in my side projects). I write a lot of tests, and 0 of them assert the type of the flowing data.
So you have no tests that would trigger an error/exception by giving bad data? I'm not saying that I'd, necessarily, call out the type explicitly, but I would give bad data to trigger the exceptional control flow/guard which can be tantamount to specifying a type. Of course, this also depends on where the function sits. If it's an internal/private function in a module that only my own functions would call I can more safely focus on the happy path. But if it's part of the interface to a module, then I want to make sure that users of the module get proper feedback/responses, whatever the contract is (be it a result type or an exception or a default value). I mean, that's a large part of the value of testing: ensuring that the code matches the specification/contract that you present to users.
Elixir/ecto has something called schema changesets that are a very robust way of validating user input. I do test against bad values (not just types, out of range, correct type but unsupported value, etc), but only really at data ingress, and no where else.

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 system can do way more than the simple things from your first code block. This is misleading in terms of what a powerful type systems can do.

Re your second code block: This can be typed with literal types, no need for tests at compile time.