Artwork . Games

Write Yourself a Scheme in Elm: Testing

November 11, 2016

Setup Your Project

The number parser is relatively straightforward, and so far testing out different input values to make sure it works correctly has not been too bad. Pretty soon there are going to be many different parsers, and you will want to make sure not to break them as you make changes to the code.

To get started with writing tests, you will need to prepare the project. There is a command elm test init that will do most of this for you, but it is important to do it yourself the first time.

First, separate the code you have written from the Main module into a new file “src/Expression.elm”:

module Expression exposing (..)

import Combine
import Combine.Char as Combine
import Combine.Num as Combine
import Combine.Infix exposing (..)
import String

type Expression
    = ENumber Float
    | EBool Bool
    | EString String
    | EIdentifier String
    | EList (List Expression)

numberParser : Combine.Parser Expression
numberParser =
    ENumber <$> Combine.choice [ Combine.float, toFloat <$> ]

readExpression : String -> String
readExpression input =
    case Combine.parse numberParser input of
        ( Ok value, _ ) ->
            "Found value: " ++ (toString value)

        ( Err messages, _ ) ->
            "No match: " ++ (String.join "\n" messages)
module Main exposing (..)

import Html exposing (..)
import Expression exposing (..)

main =
    text (readExpression "123.1")

Next, create a new directory called “tests” and copy the “elm-package.json” file into it. Open “tests/elm-package.json” and change the “source-directories” array from [ "src" ] to [ ".", "../src"]. This tells the compiler to look for elm source code in the “tests” directory as well as your “src” directory in the parent project. Finally, add the “elm-test” and “node-test-runner” packages using “elm-package”:

$ elm package install elm-community/elm-test
$ elm package install rtfeldman/node-test-runner

Write Tests

Now you are ready to start writing tests. Create a new file “tests/Expressions.elm” and paste in the following code:

module Expressions exposing (..)

import Combine
import Expect
import Expression exposing (..)
import String
import Test exposing (..)

testParser : Combine.Parser a -> String -> a -> Expect.Expectation
testParser parser input expectedValue =
    case Combine.parse parser input of
        ( Ok value, _ ) ->
            Expect.equal value expectedValue

        ( Err messages, _ ) ->
   (String.join "\n" messages)

all : Test
all =
    describe "number parser"
        [ test "positive float" <|
            \() ->
                testParser numberParser "1.1" (ENumber 1.1)

The testParser function is very similar to readExpression that we wrote earlier. It runs the parser, and tests the resulting value against an expected value using Expect.equal. If the parser fails, it uses with the combined error messages.

The Test.test function takes a string that describes the test, and a function returns an Expectation. Let’s create a new test that checks for negative float values by updating the list of tests with a new entry:

all : Test
all =
    describe "number parser"
        [ test "positive float" <|
            \() ->
                testParser numberParser "1.1" (ENumber 1.1)
        , test "negative float" <|
            \() ->
                testParser numberParser "-1.1" (ENumber -1.1)

The last thing you need to do is create a Main module that will import and run the tests you write. Create “tests/Main.elm” with the following code that you can safely ignore for now:

port module Main exposing (..)

import Expressions
import Test.Runner.Node exposing (run)
import Json.Encode exposing (Value)

main : Program Value
main =
    run emit Expressions.all

port emit : ( String, Value ) -> Cmd msg

Finally, in the parent directory, you can now run $ elm test to see if all your tests pass. The output should look something like this:


Running 2 tests. To reproduce these results, run: elm-test --seed 1289577979


Duration: 3 ms
Passed:   2
Failed:   0


  1. Write tests for positive integer “1”, and negative integer “-1”.