Not the writer, but it seems obvious to me. It’s a luxury version of assert. You leave them enabled in debug builds to get better bug reports from testers.
Those testers may hit cases you forgot to write unit tests for.
Of course, you can also forget to write invariants, or write invariants that are less tight than they should be, but I think it often is easier to write invariants than to write exhaustive unit tests.
Firstly, writing “p should always be a prime” is clearer than writing “if p is a prime, and you call f, p should be a prime afterwards”, and secondly, invariants can apply to multiple methods that you, otherwise, would have to write separate tests for (“foo keeps p a prime”, “bar keeps p a prime”, “bar keeps p a prime when called with a null argument”, “baz keeps p a prime if it throws an exception”, etc)
Also, invariants, IMO, can be way better documentation than unit tests.
Finally, invariants leave open the possibility of using a theorem prover to (dis)prove that they hold.
They are declarative vs imperative and sufficiently smart tooling exists such that they can be checked and enforced statically, see liquid haskell and "refinement" typing for an example. Formal verification starts to become a possibility -- enforcing both conformance and documentation.
> What's the advantage of invariants over unit testing?
I studied Eiffel in university under Bertrand Meyer, and here's his (probably unique) point of view.
If someone hands you their library code and their unit tests, you have to understand their unit tests - which involves understanding why they chose the values that they did. There's also nothing stopping someone from testing the internals of their library as opposed to the publicly exposed behavior.
With contracts, imagine that to understand their library, you only see the method declarations with accompanying contracts. You don't see how HashSet.Add(x) is implemented, but you do see "if this.Contains(x) old(this.Count) == this.Count".
You don't have to see a test where this is tested with 5 example values, on an empty set, on a large set, whatever. You can rid yourself of that cognitive load by thinking, as sibling points out really well, declaratively over imperatively.
icontract is one implementation of Design by Contract for Python; which is also like Eiffel, which is considered ~the origin of DbC. icontract is fancier than compile-time macros can be. In addition to Invariant checking at runtime, icontract supports inheritance-aware runtime preconditions and postconditions to for example check types and value constraints. Here are the icontract Usage docs:
https://icontract.readthedocs.io/en/latest/usage.html#invari...
For unit testing, there's icontract-hypothesis; with the Preconditions and Postconditions delineated by e.g. decorators, it's possible to generate many of the fuzz tests from the additional Design by Contract structure of the source.
> icontract-hypothesis combines design-by-contract with automatic testing.
> It is an integration between icontract library for design-by-contract and Hypothesis library for property-based testing.
> The result is a powerful combination that allows you to automatically test your code. Instead of writing manually the Hypothesis search strategies for a function, icontract-hypothesis infers them based on the function’s [sic] precondition
This isn't a replacement for unit tests. This is an alternate syntax to using asserts. You would still want unit tests to see if those asserts are being violated.
The post assertions do look very similar to a unit test, but the pre assertions seem really useful; it can sometimes be difficult to know every code path that leads to your function, and though tools exist for this, assertions on inputs help you catch errors arising from unusual conditions.
This seems like it’s mostly syntactic sugar for assertions, keeping them at the interfaces of the function (in and out).
It can also be sometimes useful to have these conditions right there alongside the implementation and not just somewhere else in your unit tests.
Because this leads the way to being able to verify that your functions will act as expected in all cases, rather than just for the ones that you thought about when you were writing unit tests.
I believe I've read about some languages with invariant using them at compile time to verify the value meets the invariant, if possible. For example, let's say a function's invariant guarantees it returns an even integer, and then we pass that into a function that only accepts odd negative integers, it could catch that during compile time. To me, that's the coolest case for invariants.
Again, I believe that this is a PL research topic, but I'm not super well versed in it, so take that with a grain of salt.
>I was ending up with garbage, and not realizing it until I had visualized it with Graphviz!
>Imagine if we had invariants that we could assert after every property change to the tree.
Have you tried writing unit tests? What didn't work about them that you decided to try invariants?