Supercharge your ExUnit skills in 10 minutes đȘ
TL;DR: Changes coming in Elixir 1.8 are amazing! I feel like in candy-shop. Seriously, read full article, it gets even better at the end :)
In this article you will learn how to use full power of mix test
runner.
If you look how to write ExUnit test casesâ you should look into my another article.Subscribe to this publication and get notified when I publish it.
I love TDD and any tool that makes my TDD process faster and more enjoyable is very welcome. Changes in Elixir 1.8 make TDD develop-save-run tests cycle even faster and better.
1. Running tests
Well, this part is pretty straightforward, or not?
Here are possible ways to run tests (clap as many times as you learned new way of running tests :) ).
mix test
â run all tests. This one is easy.mix test test/chocolate_test.exs
â run just one single file with tests. This one is also easy.mix test test/chocolate_test.exs:4
â run just one single test within test file. Equivalent ofmix test test/chocolate_test.exs --only line:4
Same test clause will run if you specify lines 5 and 6. Second test will run if you use lines 8,9,10.mix test test/chocolate_test.exs --only module:ChocolateTest
â will find and execute only tests defined in test moduleChocolateTest
. While you can runmix test --only module:ChocolateTest
that will be extremely inefficient, becauseelixir
first need to load (and compile) all test files intests
directory and then only leave only one module.mix test test/chocolate_test.exs --only test:"test ice cream"
â will find and run test by test name. Note that test name actually have âtext â prefix before it â that is added automatically byExUnit
and you need include it in the filter too.
Using this form without filename may make sense, because you may have similarly named tests â like âsuccessfully startsâ. And you can run all of them with one single filter.
If you group tests usingdescribe
â see examples below.
mix test test/chocolate_test.exs --only has_milk
â will run only tests marked with taghas_milk
(without any value). In case above it will be 2 test cases.mix test test/chocolate_test.exs --only sugar_amount:100
â will run only tests market with tagsugar_amount
with specific value.mix test test/chocolate_test.exs --exclude test --include sugar_amount:100 --include has_milk
â will run only tests marked with all included tags.--exclude test
first excludes all tests (because all of them are marked with tagtest
) and then includes all tests requested by--include
option.mix test test/chocolate_test.exs --only describe:"sweets"
â will execute tests contained in section âsweetsâ. That comes useful for all test suite, if you have same section in (all) test modules and it tests similar scenarios (likedescribe:"error messages"
to select only tests dealing with errors and so on).mix test test/chocolate_test.exs --only test:"test sweets ice cream"
â will execute only one specific test from test suite. Note that first word âtestâ comes from ExUnit itself, âsweetsâ is section name (describe) and âice creamâ is the test name. You can see full test names when runningmix test test/chocolate_test.exs --trace
.
All this richness of filters comes from the fact that ExUnit automatically assigns default tags to each test (run iex
, type h ExUnit
and read section âKnown tagsâ).
2. Speed-up test run on large code-base by using 'stale' mode
Stale is special mode or running mix that builds cross reference of modules and then executes only that tests, which include (rely) on changed modules. This includes modules directly referred by tests and modules referred by that modules and so on. So even if you change module deep in dependency tree â all modules relying on it will be marked as invalidated and tests for them will re-run.
In this case you donât need to remember which tests cover depend on module you changing right now â mix test
will do it for you.
Important notice:
If you have several tests modules in same source file (.exs) or several modules defined in same source file (.ex, like in example above) â then changing content of one of the modules in the file will cause recompilation of all modules defined in that source file and will re-run tests for all of them. So it is better to split modules and define each module and each test module in their own source file. In that way you will benefit most from mix test --stale
mode.
You can combine running tests with --listen-on-stdin
option and just press Enter
to force tests to re-run. Final command line will be
mix test --stale --listen-on-stdin
3. Speed-up test run on large code-base by using âfailedâ mode
In this mode mix test will re-run only tests that failed before. On the first run it executes whole test suite, then remembers failed tests and re-runs only them.
On a big test base it is really handy when refactoring and fixing broken tests, but it has drawback that it will not tell you if you broke any other tests, which were working before.
You also can combine running tests with --listen-on-stdin
option and just press Enter
to force tests to re-run. Final command line will be
mix test --failed --listen-on-stdin
4. Use --max-failures
to stop on failed test (starting from Elixir 1.8)
This is the most interesting part of upcoming release! I was about to implement it (see this stream), but just discovered it in documentation.
This feature allows you to stop after some number of tests in test suite failed. In TDD mode I usually run it with 1 â so it will stop on first failed test. It is equivalent of Pythonâs âpython unittest â -failfastâ.
Here is the example when all tests are run:
Here is the example with option --max-failures 1
so that test execution stops after first failed test.
5. Run in predictable sequence --seed 0
By default mix test
randomizes order in which it runs tests, so that if some tests or test suites rely on order how they are executed â it will reveal itself quite quickly. You can remember number from end of the run and then re-run them exactly in same sequence using mix test --seed THAT_NUMBER
.
In other hand when refactoring it if beneficial to have tests executed in same order as they are defined â because usually in TDD first tests are very simple and then they progress in complexity, as software was developed.
Simple tests usually are on the top of the file and it is beneficial to fix them first, before fixing more complex ones. Using option --seed 0
will always execute tests in order they are defined.
Donât forget to run test suite without this option to benefit from execution order randomization and see if there are flaky tests.
6. Ultimate powerful mix test
command-line to run tests
mix test â stale â max-failures 1 â listen-on-stdin â trace â seed 0
What does it really do?
--stale
executes only tests for source and test files which are changed.--max-failures 1
stops execution after first failing test.--listen-on-stdin
allows you press Enter and have tests re-run.--trace
prints names of tests instead of dots, so you can see which tests are executed in more descriptive way.--seed 0
forces tests to execute in same order they are mentioned in source file, so that you will have predictable consecutive runs of the tests.
7. Automatically re-run tests on file-system changes
There is amazing modules called mix-test.watch which enhances mix test
with ability to monitor file system for changes and re-running tests when something changes.
Install it using by adding it to deps
in your mix.exs
.
{:mix_test_watch, "~> 0.8", only: :dev, runtime: false}
Afterwards you can run mix test.watch
to monitor all sources and tests for changes and re-running them.
Important: Use
--seed 0
option withtest.watch
so that next run will execute tests exactly in same sequence and if you still did not fixed test case you are working on â you will see it failing.
Combine this module with previous command, especially with --max-failures 1
and you get very powerful tool for TDD.
mix test.watch --stale --max-failures 1 --trace --seed 0
8. Extra super useful clap-worthy TDD setup :) Get instant desktop notifications on test success or failure in a seconds after you save source file
There is another amazing module ex_unit_notifier which sends you desktop notifications after test suite run.
Install it using by adding following dependency to deps
in your mix.exs
.
{:ex_unit_notifier, "~> 0.1", only: :test}
Also add this line to test/test_helper.exs
ExUnit.configure formatters: [ExUnit.CLIFormatter, ExUnitNotifier]
ExUnit.start
Then run test suite using mix test.watch
:
mix test.watch --stale --max-failures 1 --trace --seed 0
Magic! You will see in a seconds after saving file notification popping up with test suite run statistics. Here is notifications after fixing tests one by one until all test suite passes.
About me
Iâm Gaspar Chilingarov . I facilitate DevOps transition, help moving legacy applications to cloud and write high-performance Elixir apps.
Need help with your Elixir app or want prototype your next microservice in Elixir? DM me on Twitter or Github.
You can connect with me on Twitter, Facebook, LinkedIn and GitHub.
Found this post useful? Kindly tap the †button below! :) Letâs spread word about Elixir.