I've recently grown super fond of using the command pattern to write quick to author, and easy to understand tests.
The gist is that you define a list of 'commands' that your application can logically perform that you find interesting to model.
So for example I'm currently writing an MQTT client, so 'connect', 'publish', 'server disconnect', etc... are all commands that make sense to have in some fashion.
This then allows you to really quickly write down tests whenever you encounter some weird behaviour, or are wondering yourself 'what happens if'.
I'm using lots of async, and having a single-threaded executor allows a lot of control over timings, and finding out bugs that can potentially cause dead-locks (by effectively dead-locking in single threading)
The main benefit really is just writing more tests overall! You can then go more crazy and for example permute difference sequences. This allows you to get more coverage of various code-paths.
In one application I was writing, I had multiple clients connect (a multi step process), send a message (another multi step process), and then disconnect.
This unearthed some locking badness, where I was holding onto DashMap read-locks for too long at the wrong places. (And part of the reason why I wrote a custom dylint-lint to detect that cf.: https://git.hemera.systems/Hemera/dylint-various/src/branch/main/lints/dashmap-ref/src/lib.rs )
Its of course not a panacea, but it is a nice tool! I'm trying to see how far it can go together with check-point testing