Skip to content

DescribeSML is a BDD style testing framework for Standard ML

License

Notifications You must be signed in to change notification settings

emilwall/DescribeSML

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

DescribeSML

DescribeSML is a BDD style testing framework for Standard ML of New Jersey, similar to RSpec and Jasmine. Example:

(* Import DescribeSML *)
CM.make "describe.cm";
open Describe;
open Expect;

(* Define system under test *)
fun hello you = concat ["Hello ", you, "!"];

(* State expectations *)
suite (describe "hello" [
    should ("say Hello World! when given World as argument", fn () =>
        expect (hello "World")
            toEqualStr "Hello World!"),

    should ("always return string ending with exclamation mark", fn () =>
        expect (hello "")
            toMatch "!$")
])

(* Output:
Ran 2 specs for hello:

..

Total failures: 0
================= *)

This testing framework was developed as part of my thesis Rationales and Approaches for Automated Testing of JavaScript and Standard ML, and not actively maintained. Feel free to add a pull request if you have made an improvement.

DescribeSML uses the SML/NJ Compilation and Library Manager (CM), so there is a lot of flexibility regarding how to use it. example-specs contains some example specs (tests) of simple functions, using DescribeSML. They use relative references to the .cm file and calls open on the exported structures, so that describe and should can be used instead of DescribeSML.describe and DescribeSML.should.

DescribeSML consists of the Describe structure, which includes error reporting utilities and the ability to have nested describes, and the Expect structure, which contains matchers such as toBe, toEqualInt, toContain, toMatch and toThrow. There are no dependencies between the structures except that the Describe functions expect matchers to return a string beginning with either "pass" or "FAIL". This means that you can use them seperately, replacing one of them with your own implementation, if you feel like it.

Here is an example of how DescribeSML can be used (same as here):

CM.make "../describe.cm";
open Describe;
open Expect;
use "../example-src/robberlanguage.sml";

suite (describe "RobberLanguage" [
describe "isConsonant"
    [should("consider b to be a consonant", fn () =>
        expect (RobberLanguage.isConsonant #"b") toBe true),

     should("consider capital letters as consonants", fn () =>
        expect (RobberLanguage.isConsonant #"B") toBe true),

     should("not consider vowels as consonants", fn () =>
        expect (RobberLanguage.isConsonant #"a") toBe false)],

describe "translate"
    [should("repeat consonant with the letter o inserted in between", fn () =>
        expect (RobberLanguage.translate "s")
            toBeStr "sos"),

     should("not repeat vowels", fn () =>
        expect (RobberLanguage.translate "a")
            toHaveSize 1),

     should("work for long strings", fn () =>
        expect (RobberLanguage.translate "a long string")
            toMatch "lolonongog"),

     should("repeat capital consonants as lower case", fn () =>
        expect (RobberLanguage.translate "Astrid Lindgren")
            toEqualStr "Asostotroridod Lolinondodgogrorenon")]
])

If we have this (intentionally buggy) implementation:

structure RobberLanguage :>
sig
   val isConsonant : char -> bool
   val translate : string -> string
end =
struct
    val consonants = explode "cdfghjklmnpqrstvwxz"

    fun isConsonant c =
        List.exists (fn c' => c = c' orelse Char.toLower c = c') consonants

    fun process [] = []
      | process (c::cs) =
        if isConsonant c
        then (Char.toUpper c) :: #"o" :: c :: process cs
        else c :: process cs

    val translate = implode o process o explode
end

You get the following output from running the specs:

Ran 2 nested describes (total 7 specs) for RobberLanguage:

!!

Ran 3 specs for isConsonant:

!!.

should consider b to be a consonant: FAIL
should consider capital letters as consonants: FAIL

Ran 4 specs for translate:

!.!!

should repeat consonant with the letter o inserted in between: FAIL: expected "Sos" to equal "sos"
should work for long strings: FAIL: expected "a LoloNonGog SosTotRoriNonGog" to match "lolonongog"
should repeat capital consonants as lower case: FAIL: expected "ASosTotRoriDod LoLiNonDodGogRoreNon" to equal "Asostotroridod Lolinondodgogrorenon"

Total failures: 5
=================

This allows you to get detailed error reports for nested describes, with a wide variety of matchers.

Fixing the bugs in the implementation:

val consonants = explode "bcdfghjklmnpqrstvwxz"
then c :: #"o" :: (Char.toLower c) :: process cs

yields

Ran 2 nested describes (total 7 specs) for RobberLanguage:

..

Total failures: 0
=================

If you have any questions, feedback or requests, please post an issue here on github or email me at emilwall89 at gmail.com

About

DescribeSML is a BDD style testing framework for Standard ML

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published