This week's about tooling, so let's have a look at what I usually consider the most important tool for a given language: a test framework. We'll be using elm-test. Let's get started.

Project

We'll start out in the frogger repo, which I've tagged with before_episode_008.4.

We'll start out by making a tests directory.

mkdir tests
cd tests

We'll install elm-test:

elm-package install elm-community/elm-test

Now we'll make a Tests.elm file:

vim Tests.elm

We'll just copy the example Tests.elm file from the elm-test repo for now:

module Tests exposing (..)

import List
import ElmTest exposing (..)


tests : List Test
tests =
    [ 0 `equals` 0
    , test "pass" <| assert True
    , test "fail" <| assertNotEqual True False
    ]
        ++ (List.map defaultTest <| assertionList [1..10] [1..10])


consoleTests : Test
consoleTests =
    suite "All Tests" tests


main =
    runSuite consoleTests

We'll look at it in detail in a moment. For now, let's just run the tests.

You can run them in the console out of the gate in elm-test since it was updated for 0.17. First, we have to compile a js file:

elm-make Tests.elm --output tests.js

Now we can run it with node:

node tests.js
  1 suites run, containing 13 tests
  All tests passed


: ""

Now let's look at the elm file to see how you write tests:

vim Tests.elm
-- We make a normal module.
module Tests exposing (..)

-- We import ElmTest
import List
import ElmTest exposing (..)


-- We'll make a tests function that produces a list of tests
tests : List Test
tests =
-- `equals` will make a test that asserts equality between two arguments.  Here,
-- we're using it in infix style.
    [ 0 `equals` 0
-- `test` takes a test name and an assertion.  Here a backwards pipe is being
-- used to avoid parenthesis, but you can also just wrap the assertion in
-- parens.
-- `assert` will produce a passing test if its argument is True
    , test "pass" <| assert True
-- `assertNotEqual` will pass if the arguments are not equal
    , test "fail" <| assertNotEqual True False
    ]
-- `defaultTest` will generate the test name for you based on the inputs.
-- Here, we map that function across a list of assertions.
-- `assertionList` takes two lists of equal length and produces a list of
-- assertions that each matching element in the lists is equal.
-- So here we're just mapping over those assertions with `defaultTest` to
-- produce a list of tests for those assertions with auto-generated names.
        ++ (List.map defaultTest <| assertionList [1..10] [1..10])


-- `suite` takes a name and a list of tests, and produces a new test that wraps
-- all of those in one chunk of execution.
consoleTests : Test
consoleTests =
    suite "All Tests" tests


-- And our main function passes our suite to the `runSuite` function, which will
-- run a suite of tests as a program.
main =
    runSuite consoleTests

Let's play around a bit to get comfortable with everything. We'll extract that assertionList line into its own function:

tests : List Test
tests =
    [ 0 `equals` 0
    , test "pass" (assert True)
    , test "fail" <| assertNotEqual True False
    ]


assertionListTests : List Test
assertionListTests =
    (List.map defaultTest <| assertionList [1..10] [1..10])

We'll rename tests to basicTests:

basicTests : List Test
basicTests =
    -- ...

We'll rename consoleTests to basicTestSuite:

basicTestSuite : Test
basicTestSuite =
    suite "Basic Tests" basicTests

We'll add a listTestSuite:

listTestSuite : Test
listTestSuite =
    suite "List Tests" assertionListTests

We'll make an allTestSuite that wraps each of those suites into a parent suite, showing that since suites are of type Test they can be trivially nested:

allTestSuite : Test
allTestSuite =
    suite "All Tests" [ basicTestSuite, listTestSuite ]

And we'll update our main to run the new parent test suite:

main =
    runSuite allTestSuite

Let's go ahead and compile it and make sure it all runs still:

elm-make Tests.elm --output tests.js

And we'll run it again:

node tests.js
  3 suites run, containing 13 tests
  All tests passed


: ""

So here we can see that the output is aware of how many suites we have.

The only function we really haven't covered is lazyAssert. It takes a function from the unit to a Bool, but delays execution. We'll add one just for an example:

    , test "lazy" <| lazyAssert (\_ -> 6 > 5)

Next, let's go back to the root of the project and add a basic script to run our tests:

cd ..
echo "pushd tests && elm-make Tests.elm --output tests.js && node tests.js && popd" > ./test
chmod u+x test

Now we can run it:

./test

Finally, it's always nice to set up CI for your tests. We can do this with Travis CI fairly easily. Make a .travis.yml file:

language: node_js
node_js:
  - "5"
install:
  - npm install -g elm
  - elm-package install -y
  - pushd tests && elm-package install -y && popd
script:
  - cd tests && elm-make Tests.elm --output tests.js && node tests.js

If you push that up, travis will run the tests for every commit and report back to you. You can see an example build here

Summary

Alright, in today's episode we saw how to use elm-test to write our tests and run them easily on the command line. We also saw how to run our tests with Travis CI. I hope you enjoyed it. See you soon!

Resources