Search code examples
erlangeunit

Is it considered good practice to include EUnit tests within an Erlang module?


There seem to be two common approaches to using EUnit:

  1. Include the tests themselves at the end of a module
  2. Add the tests to a separate 'test' path

Assuming I am only interested in testing the exported functions of a particular module, is there any advantage or convention in picking one approach over the other?


Solution

  • tl;dr: You can usually put them at the bottom of the source without mucking things up. Interleaving always sucks. Writing so many tests that you need a special place for gobs of test-specific code means you've misinvested your time.

    Breaking it out shortens your source files and makes things easier to navigate in some cases. OTOH, difficult to navigate source is usually a sign of other, deeper issues.

    I have found Dialyzer and users to be my real bug finders, and tests of relatively little utility aside from purely functional, zero side-effect code. In the case of purely functional code the tests are trivial -- once that sort of code is checked its a good bet nobody will have to revisit it for ages and ages, if ever. In those cases I usually include tests at the end so I don't lose them if we want to go back and find them again. I make a conscious effort to refactor until as much of my code as possible is of this type -- not everyone agrees this is worth the effort.

    The hairy parts of the code are the side-effecty bits, especially if you haven't done some work with typer and Dialyzer on it to find the most major offenses you've been committing against yourself. The only thing all real-world bugs have in common is that they already passed all the tests. I've never found any correlation between effort spent writing tests and bugginess in side-effecty code. Actually, there is no way to formally test actor-model protocols at the moment, and interactions is where the remaining bugs lie usually, not hidden within processes you've already thought carefully about.

    To put this in concrete perspective, consider this: Would you rather work on a system someone had spent X hours typing properly, or X hours writing and running tests on? My vote goes for typing. I'm not saying that testing is bad, I'm saying it is expensive in developer time and of limited utility outside purely functional code. Tests for purely functional code have clearly identifiable limits, tend to be trivial and small, and so can unobtrusively reside at the bottom of the modules to which they apply.