ExUnit uses a wonderful utility of Elixir to provide an elegant way to organize and group tests. This feature is the module attribute.

This has proven invaluable as our systems and test suites we build grow bigger & more complex. It makes testing new features more efficient and fast. Using this we don't have to run every other tests (which could be thousands) in the system.

We'll be writing a test suite for an imaginary mathematical module.

The first attribute is @tag which marks a single test case:

defmodule MathTest do
  use ExUnit.Case
  
  # ...
  
  @tag :this
  test "that we can add two numbers together" do
    assert 2 == Math.add(1, 1)
  end
  
  # ...
end

Even if we have thousands of test cases, we can run only the tagged test with mix test --only this.

That's all fine and good, but what if I want to run more than one test? Good question. ExUnit has you covered.

Let's say we wanted to test all associative operations in our module. We organize those tests into groups with describe and annotate them with @describetag:

defmodule MathTest do
  use ExUnit.Case
  
  describe "associative functions" do
    @describetag math: "associative"
    
    test "that addition is associative" do
      assert Math.add(1, 2) == Math.add(2, 1)
    end
    
    # ...
  end
  
  describe "commutative functions" do
    @describetag math: "commutative"
    
    test "that multiplication is commutative" do
      assert Math.multiply(3, 2) == Math.multiply(2, 3)
    end
    
    # ...
  end
  
  # ...
end

In our test suite, we have described two groups of tests. If we wanted to run only the associative tests, we could do so with mix test --only math:"associative". Likewise, mix test --only math:"commutative" runs only the tests in the "commutative functions" group.

Notice that there's no space between math: and "commutative" in the test command.

Ok, that's cool. What if I wanted to run all test groups defined in a file? Another good question. Again, tags to the rescue!.

To run all tests in a file, we use @moduletag:

defmodule MathTest do
  use ExUnit.Case
  
  @moduletag :math
  
  describe "associativity" do
    @describetag math: "associative"
    
    test "that addition is associative" do
      assert Math.add(1, 2) == Math.add(2, 1)
    end
    
    # ...
  end
  
  describe "commutativity" do
    @describetag math: "commutative"
    
    test "that multiplication is commutative" do
      assert Math.multiply(3, 2) == Math.multiply(2, 3)
    end
    
    # ...
  end
  
  @tag :this
  test "that we can add two numbers together" do
    assert 2 == Math.add(1, 1)
  end
  
  # ...
end

mix test --only math will run all tests in the MathTest file.

While we've used simple illustrations, @tag, @describetag and @moduletag are Lego blocks that can be combined in various ways.