Writing better tests with less work
Status: Accepted
Many developers feel that writing tests is just busywork — a distraction from the real work of writing the code that users will run. And they're not wrong: in too many codebases, too many of the tests really are busywork. A busywork test doesn't meaningfully help catch bugs, and consumes effort not only to write the test but to update it again and again as things change in the codebase.
But it doesn't have to be that way. The right techniques can make a test more effective at catching bugs now, and preventing regressions next month and next year. The same techniques can greatly reduce the burden of maintaining tests as the codebase evolves, and make the tests easier to read, with less code, even while being more comprehensive. Done right, tests are an affordable investment today that lets your team develop faster and more confidently tomorrow, and deliver a higher-quality product.
The core techniques apply in any language or framework. Flutter offers especially rich support for writing high-quality tests right up through the UI layer, as we'll see.
In this talk, I'll share the principles I've seen work, with practical tips and examples from real-world code:
* How to choose what layer of API a given test should operate at (beyond the binary of "unit test" vs. "integration test"), to make it both low-maintenance and high-signal.
* The specific tools from Flutter's rich test framework that enable writing tests at a range of different layers.
* How to factor out the boring setup from each test case in ways that make the test easier to understand, not harder, and enable writing more tests with less code and less maintenance.
* How to imitate Flutter's own "binding" system (a form of dependency injection) to let tests cleanly simulate network requests, your database, and all the Flutter plugins you use, and how to apply package:fake_async to manipulate time itself.
* The package:checks library, an alternative to `expect` designed for modern Dart and backward-compatible with `expect`-style matchers.
I've put these principles into practice in Zulip, and coached numerous contributors to write high-quality tests there. Some points are inspired by my experience in the upstream Flutter tree itself, which is relentless about testing all kinds of changes, and I'll draw examples from there too.