Introducing VUnit: a JUnit inspired unit testing framework.
Unit testing is the act of testing single software components, such as functions, in isolation and is generally useful in efficiently detecting bugs occurring strictly within such.
Persistent test suites are useful when testing software under long-term development since it allows one to store and re-run tests after modifications in an orderly fashion.
A unit testing framework is practical because it makes available abstractions for creating, organizing, running, re-running, and summarizing the results of unit tests thus allowing test suites to scale more or less flawlessly as the inspected software grows. Tests maintained by testing frameworks are known as automated tests and comprise a key component in test driven development (TDD).
Simple example based on a VUnit script for Vectors:
(Admittedly naive) self-test and (admittedly intricate) demonstration:
http://krainert.com/content/vunit/
Unit testing is the act of testing single software components, such as functions, in isolation and is generally useful in efficiently detecting bugs occurring strictly within such.
Persistent test suites are useful when testing software under long-term development since it allows one to store and re-run tests after modifications in an orderly fashion.
A unit testing framework is practical because it makes available abstractions for creating, organizing, running, re-running, and summarizing the results of unit tests thus allowing test suites to scale more or less flawlessly as the inspected software grows. Tests maintained by testing frameworks are known as automated tests and comprise a key component in test driven development (TDD).
JASS:
library VUnit
//********************************************************************************************************************************
//* VUnit
//*
//* ABOUT
//* Author: krainert
//* Version: 2.0
//*
//* DESCRIPTION
//* A JUnit inspired unit testing framework
//*
//* STRUCTS
//* ======== VUnitSuite ========
//* -- Description
//* A runnable test suite
//* -- Methods
//* static VUnitSuite create(string name) - returns a new test suite named 'name'
//* static VUnitSuite createSilent() - returns a new test suite which will not output any messages
//* void addSuiteInitializer(VUnitSuiteInitializer init) - adds suite initializer 'init'
//* void addSuiteTerminator(VUnitSuiteTerminator term) - adds suite terminator 'term'
//* void addInitializer(VUnitInitializer init) - adds test initializer 'init'
//* void addTerminator(VUnitTerminator term) - adds test terminator 'term'
//* void addTest(VUnitTest test, string name) - adds test test named 'name'
//* boolean run() - runs all tests and returns whether all of them passed
//* boolean fail(string description) - fails with description 'description' and returns false
//* boolean assertTrue(boolean condition, string description) - asserts 'condition'==true with description 'description' and returns whether this assertion holds
//* boolean assertFalse(boolean condition, string description) - asserts 'condition'==false with description 'description' and returns whether this assertion holds
//* boolean assertBooleanEquals(boolean expected, boolean actual, string description) - asserts 'actual'=='expected' with description 'description' and returns whether thisassertion holds
//* boolean assertIntegerEquals(integer expected, integer actual, string description) - asserts 'actual'=='expected' with description 'description' and returns whether this assertion holds
//* boolean assertRealEquals(real expected, real actual, real delta, string description) - asserts 'actual'=='expected' with a deviation of at most 'delta' and description 'description' and returns whether this assertion holds
//* boolean assertStringEquals(string expected, string actual, string description) - asserts 'actual'=='expected' with description 'description' and returns whether this assertion holds
//*
//* FUNCTION INTERFACES
//* void VUnitSuiteInitializer() - initializes test suite
//* void VUnitSuiteTerminator() - terminates test suite
//* void VUnitInitializer() - initializes test
//* void VUnitTerminator() - terminates test
//* void VUnitTest(VUnitSuite suite) - performs a test within test suite 'suite'
//*
//* CHANGELOG
//* 1.0 2011-10-29 Original release
//* 1.1 2011-10-30 Added support for test suite initializers and terminators
//* Minor internal refactoring
//* 2.0 2011-01-16 Hid and renamed previous fail method and substituted new one
//* Documentation updated
//*
//********************************************************************************************************************************
globals
private constant integer MAX_SUITES = 8191
endglobals
function interface VUnitSuiteInitializer takes nothing returns nothing
function interface VUnitSuiteTerminator takes nothing returns nothing
function interface VUnitInitializer takes nothing returns nothing
function interface VUnitTerminator takes nothing returns nothing
function interface VUnitTest takes VUnitSuite suite returns nothing
private function B2S takes boolean bool returns string
if (bool) then
return "true"
else
return "false"
endif
endfunction
struct VUnitSuite[MAX_SUITES]
private string name
private boolean audiable
private integer siniCount = 0
private integer sterCount = 0
private integer initCount = 0
private integer termCount = 0
private integer testCount = 0
private hashtable sinis = InitHashtable()
private hashtable sters = InitHashtable()
private hashtable inits = InitHashtable()
private hashtable terms = InitHashtable()
private hashtable tests = InitHashtable()
private boolean currentTestPassed
static method create takes string name returns thistype
local thistype r = .allocate()
set r.name = name
set r.audiable = true
return r
endmethod
static method createSilent takes nothing returns thistype
local thistype r = .allocate()
set r.audiable = false
return r
endmethod
method onDestroy takes nothing returns nothing
call FlushParentHashtable(.sinis)
call FlushParentHashtable(.sters)
call FlushParentHashtable(.inits)
call FlushParentHashtable(.terms)
call FlushParentHashtable(.tests)
endmethod
private method output takes string message returns nothing
if (.audiable) then
call DisplayTimedTextToPlayer(GetLocalPlayer(), 0., 0., 30., "|cff7777aa[VUnit]|r " + message)
endif
endmethod
method addSuiteInitializer takes VUnitInitializer init returns nothing
call SaveInteger(.sinis, .siniCount, 0, init)
set .siniCount = .siniCount + 1
endmethod
method addSuiteTerminator takes VUnitTerminator term returns nothing
call SaveInteger(.sters, .sterCount, 0, term)
set .sterCount = .sterCount + 1
endmethod
method addInitializer takes VUnitInitializer init returns nothing
call SaveInteger(.inits, .initCount, 0, init)
set .initCount = .initCount + 1
endmethod
method addTerminator takes VUnitTerminator term returns nothing
call SaveInteger(.terms, .termCount, 0, term)
set .termCount = .termCount + 1
endmethod
method addTest takes VUnitTest test, string name returns nothing
call SaveInteger(.tests, .testCount, 0, test)
call SaveStr(.tests, .testCount, 1, name)
set .testCount = .testCount + 1
endmethod
method run takes nothing returns boolean
local boolean suitePassed = true
local integer i = 0
local integer j
local VUnitSuiteInitializer sini
local VUnitSuiteTerminator ster
local VUnitInitializer init
local VUnitTerminator term
local VUnitTest test
local string name
call .output("|cffffcc00Suite: '" + .name + "' (" + I2S(.testCount) + " tests)|r")
loop
exitwhen i == .siniCount
set sini = LoadInteger(.sinis, i, 0)
call sini.evaluate()
set i = i + 1
endloop
set i = 0
loop
exitwhen i == .testCount
//initializers
set j = 0
loop
exitwhen j == .initCount
set init = LoadInteger(.inits, j, 0)
call init.evaluate()
set j = j + 1
endloop
//test
set test = LoadInteger(.tests, i, 0)
set name = LoadStr(.tests, i, 1)
set .currentTestPassed = true
call test.evaluate(this)
if (.currentTestPassed) then
call .output("Test '" + name + "' passed")
else
set suitePassed = false
call .output("|cff995500Test '" + name + "' failed|r")
endif
//terminators
set j = 0
loop
exitwhen j == .termCount
set term = LoadInteger(.terms, j, 0)
call term.evaluate()
set j = j + 1
endloop
set i = i + 1
endloop
set i = 0
loop
exitwhen i == .sterCount
set ster = LoadInteger(.sters, i, 0)
call ster.evaluate()
set i = i + 1
endloop
if (suitePassed) then
call .output("|cff32cd32Suite '" + .name + "' passed|r")
return true
else
call .output("|cffff0000Suite '" + .name + "' failed|r")
return false
endif
endmethod
private method reject takes string description, string elaboration returns nothing
set .currentTestPassed = false
call .output("|cff995500Assertion '" + description + "' violated; " + elaboration + "|r")
endmethod
method fail takes string description returns boolean
call .reject(description, "fail()")
return false
endmethod
method assertTrue takes boolean condition, string description returns boolean
if (condition) then
return true
else
call .reject(description, "assertTrue(" + B2S(condition) + ")")
return false
endif
endmethod
method assertFalse takes boolean condition, string description returns boolean
if (not(condition)) then
return true
else
call .reject(description, "assertFalse(" + B2S(condition) + ")")
return false
endif
endmethod
method assertBooleanEquals takes boolean expected, boolean actual, string description returns boolean
if (actual == expected) then
return true
else
call .reject(description, "assertBooleanEquals(" + B2S(expected) + "," + B2S(actual) + ")")
return false
endif
endmethod
method assertIntegerEquals takes integer expected, integer actual, string description returns boolean
if (actual == expected) then
return true
else
call .reject(description, "assertIntegerEquals(" + I2S(expected) + "," + I2S(actual) + ")")
return false
endif
endmethod
method assertRealEquals takes real expected, real actual, real delta, string description returns boolean
if (actual >= expected - delta and actual <= expected + delta) then
return true
else
call .reject(description, "assertRealEquals(" + R2S(expected) + "," + R2S(actual) + "," + R2S(delta) + ")")
return false
endif
endmethod
method assertStringEquals takes string expected, string actual, string description returns boolean
if (actual == expected) then
return true
else
call .reject(description, "assertStringEquals(" + expected + "," + actual + ")")
return false
endif
endmethod
endstruct
endlibrary
Simple example based on a VUnit script for Vectors:
JASS:
library VectorsVUnit initializer ini requires Vectors, VUnit
private function vector2D_dotProduct takes VUnitSuite suite returns nothing
local Vector2D v1 = Vector2D.createXY(2., 2.5)
local Vector2D v2 = Vector2D.createXY(5., 3.)
call suite.assertRealEquals(17.5, v1.dotProduct(v2), .001, "dot product of (2, 2.5) and (5, 3)")
call v1.destroy()
call v2.destroy()
endfunction
private function vector2D_scale takes VUnitSuite suite returns nothing
local Vector2D v = Vector2D.createXY(-42., 24.)
call v.scale(-.25)
call suite.assertRealEquals(10.5, v.getX(), .001, "x value of (-42, 24) scaled by -0.25")
call suite.assertRealEquals(-6., v.getY(), .001, "y value of (-42, 24) scaled by -0.25")
call v.destroy()
endfunction
private function vector3D_angle takes VUnitSuite suite returns nothing
local Vector3D v1 = Vector3D.createXYZ(2., 2.5, 4.)
local Vector3D v2 = Vector3D.createXYZ(5., 3., 1.23)
call suite.assertRealEquals(0.746151, v1.angle(v2), .01*0.123221, "angle between (2, 2.5, 4) and (5, 3, 1.23)")
call v1.destroy()
call v2.destroy()
endfunction
private function vector3D_addXYZ takes VUnitSuite suite returns nothing
local Vector3D v = Vector3D.createXYZ(12.101, 34.123, .123)
call v.addXYZ(56.303, -78.22, .321)
call suite.assertRealEquals(68.404, v.getX(), .001, "x value of (12.101, 34.123, 0.123) + (56.303, -78.22, 0.321)")
call suite.assertRealEquals(-44.097, v.getY(), .001, "y value of (12.101, 34.123, 0.123) + (56.303, -78.22, 0.321)")
call suite.assertRealEquals(.444, v.getZ(), .001, "z value of (12.101, 34.123, 0.123) + (56.303, -78.22, 0.321)")
call v.destroy()
endfunction
private function ini takes nothing returns nothing
//suites
local VUnitSuite vector2D = VUnitSuite.create("Vector2D")
local VUnitSuite vector3D = VUnitSuite.create("Vector3D")
//Vector2D
call vector2D.addTest(vector2D_dotProduct, "dotProduct")
call vector2D.addTest(vector2D_scale, "scale")
//Vector3D
call vector3D.addTest(vector3D_angle, "angle")
call vector3D.addTest(vector3D_addXYZ, "addXYZ")
//run
call TriggerSleepAction(0)
call vector2D.run()
call vector3D.run()
call vector2D.destroy()
call vector3D.destroy()
endfunction
endlibrary
(Admittedly naive) self-test and (admittedly intricate) demonstration:
JASS:
library VUnitVUnit initializer ini requires VUnit
globals
private VUnitSuite mother
private string s1
private string s2
private string s3
private string s4
endglobals
private function vUnitSuite_init takes nothing returns nothing
set s1 = "default"
set s2 = "default"
set s3 = "default"
set s4 = "default"
endfunction
private function vUnitSuite_run takes VUnitSuite suite returns nothing
local VUnitSuite s = VUnitSuite.createSilent()
call suite.assertTrue(s.run(), "run() on VUnitSuite containing no tests should return true")
call s.destroy()
endfunction
private function vUnitSuite_initterm_sini1 takes nothing returns nothing
set s1 = "vUnitSuite_initterm_sini1"
set s2 = "vUnitSuite_initterm_sini1"
endfunction
private function vUnitSuite_initterm_sini2 takes nothing returns nothing
set s3 = "vUnitSuite_initterm_sini2"
set s4 = "vUnitSuite_initterm_sini2"
endfunction
private function vUnitSuite_initterm_ster1 takes nothing returns nothing
set s2 = "vUnitSuite_initterm_ster1"
endfunction
private function vUnitSuite_initterm_ster2 takes nothing returns nothing
set s4 = "vUnitSuite_initterm_ster2"
endfunction
private function vUnitSuite_initterm_init1 takes nothing returns nothing
set s2 = "vUnitSuite_initterm_init1"
endfunction
private function vUnitSuite_initterm_init2 takes nothing returns nothing
set s4 = "vUnitSuite_initterm_init2"
endfunction
private function vUnitSuite_initterm_term1 takes nothing returns nothing
set s1 = "vUnitSuite_initterm_term1"
set s2 = "vUnitSuite_initterm_term1"
endfunction
private function vUnitSuite_initterm_term2 takes nothing returns nothing
set s3 = "vUnitSuite_initterm_term2"
set s4 = "vUnitSuite_initterm_term2"
endfunction
private function vUnitSuite_initterm_test takes VUnitSuite suite returns nothing
call mother.assertStringEquals("vUnitSuite_initterm_sini1", s1, "s1 should be \"vUnitSuite_initterm_sini1\" at the beginning of tests within VUnitSuite with vUnitSuite_initterm_sini1 added as suite initializer")
call mother.assertStringEquals("vUnitSuite_initterm_init1", s2, "s2 should be \"vUnitSuite_initterm_init1\" at the beginning of tests within VUnitSuite with vUnitSuite_initterm_sini1 and vUnitSuite_initterm_init1 added as suite and test initializers, respectively")
call mother.assertStringEquals("vUnitSuite_initterm_sini2", s3, "s3 should be \"vUnitSuite_initterm_sini2\" at the beginning of tests within VUnitSuite with vUnitSuite_initterm_sini2 added as suite initializer")
call mother.assertStringEquals("vUnitSuite_initterm_init2", s4, "s4 should be \"vUnitSuite_initterm_init2\" at the beginning of tests within VUnitSuite with vUnitSuite_initterm_sini2 and vUnitSuite_initterm_init2 added as suite and test initializers, respectively")
endfunction
private function vUnitSuite_initterm takes VUnitSuite suite returns nothing
local VUnitSuite s
call suite.assertIntegerEquals(suite, mother, "assumption: mother should be the VUnitSuite in which this test resides")
call suite.assertStringEquals("default", s1, "assumption: s1 should be \"default\" at the beginning of this test")
call suite.assertStringEquals("default", s2, "assumption: s2 should be \"default\" at the beginning of this test")
call suite.assertStringEquals("default", s3, "assumption: s3 should be \"default\" at the beginning of this test")
call suite.assertStringEquals("default", s4, "assumption: s4 should be \"default\" at the beginning of this test")
set s = VUnitSuite.createSilent()
call s.addSuiteInitializer(vUnitSuite_initterm_sini1)
call s.addSuiteInitializer(vUnitSuite_initterm_sini2)
call s.addSuiteTerminator(vUnitSuite_initterm_ster1)
call s.addSuiteTerminator(vUnitSuite_initterm_ster2)
call s.addInitializer(vUnitSuite_initterm_init1)
call s.addInitializer(vUnitSuite_initterm_init2)
call s.addTerminator(vUnitSuite_initterm_term1)
call s.addTerminator(vUnitSuite_initterm_term2)
call s.addTest(vUnitSuite_initterm_test, "")
call s.run()
call s.destroy()
call mother.assertStringEquals("vUnitSuite_initterm_term1", s1, "s1 should be \"vUnitSuite_initterm_term1\" after running VUnitSuite with vUnitSuite_initterm_term1 added as test terminator")
call mother.assertStringEquals("vUnitSuite_initterm_ster1", s2, "s2 should be \"vUnitSuite_initterm_ster1\" after running VUnitSuite with vUnitSuite_initterm_ster1 and vUnitSuite_initterm_term1 added as suite and test terminators, respectively")
call mother.assertStringEquals("vUnitSuite_initterm_term2", s3, "s3 should be \"vUnitSuite_initterm_term2\" after running VUnitSuite with vUnitSuite_initterm_term2 added as test terminator")
call mother.assertStringEquals("vUnitSuite_initterm_ster2", s4, "s4 should be \"vUnitSuite_initterm_ster2\" after running VUnitSuite with vUnitSuite_initterm_ster2 and vUnitSuite_initterm_term2 added as suite and test terminators, respectively")
endfunction
private function vUnitSuite_assertSatisfied_true takes VUnitSuite suite returns nothing
call suite.assertTrue(true, "")
endfunction
private function vUnitSuite_assertSatisfied_false takes VUnitSuite suite returns nothing
call suite.assertFalse(false, "")
endfunction
private function vUnitSuite_assertSatisfied_booleanEquals takes VUnitSuite suite returns nothing
call suite.assertBooleanEquals(true, true, "")
call suite.assertBooleanEquals(false, false, "")
endfunction
private function vUnitSuite_assertSatisfied_integerEquals takes VUnitSuite suite returns nothing
call suite.assertIntegerEquals(0, 0, "")
call suite.assertIntegerEquals(12, 12, "")
call suite.assertIntegerEquals(-34, -34, "")
endfunction
private function vUnitSuite_assertSatisfied_realEquals takes VUnitSuite suite returns nothing
call suite.assertRealEquals(0., 0., .1, "")
call suite.assertRealEquals(12., 12., .1, "")
call suite.assertRealEquals(-34., -34., .1, "")
call suite.assertRealEquals(56., 56.075, .1, "")
endfunction
private function vUnitSuite_assertSatisfied_stringEquals takes VUnitSuite suite returns nothing
call suite.assertStringEquals("", "", "")
call suite.assertStringEquals("abc", "abc", "")
call suite.assertStringEquals("DEF", "DEF", "")
call suite.assertStringEquals("gHi", "gHi", "")
endfunction
private function vUnitSuite_assertSatisfied takes VUnitSuite suite returns nothing
local VUnitSuite s = VUnitSuite.createSilent()
call s.addTest(vUnitSuite_assertSatisfied_true, "")
call s.addTest(vUnitSuite_assertSatisfied_false, "")
call s.addTest(vUnitSuite_assertSatisfied_booleanEquals, "")
call s.addTest(vUnitSuite_assertSatisfied_integerEquals, "")
call s.addTest(vUnitSuite_assertSatisfied_realEquals, "")
call s.addTest(vUnitSuite_assertSatisfied_stringEquals, "")
call suite.assertTrue(s.run(), "run() on VUnitSuite containing purely passable tests should return true")
call s.destroy()
endfunction
private function vUnitSuite_failViolated_test takes VUnitSuite suite returns nothing
call suite.fail("")
endfunction
private function vUnitSuite_failViolated takes VUnitSuite suite returns nothing
local VUnitSuite s = VUnitSuite.createSilent()
call s.addTest(vUnitSuite_failViolated_test, "")
call suite.assertFalse(s.run(), "run() on VUnitSuite containing a test violating a fail assertion should return false")
call s.destroy()
endfunction
private function vUnitSuite_assertTrueViolated_test takes VUnitSuite suite returns nothing
call suite.assertTrue(false, "")
endfunction
private function vUnitSuite_assertTrueViolated takes VUnitSuite suite returns nothing
local VUnitSuite s = VUnitSuite.createSilent()
call s.addTest(vUnitSuite_assertTrueViolated_test, "")
call suite.assertFalse(s.run(), "run() on VUnitSuite containing a test violating an assertTrue assertion should return false")
call s.destroy()
endfunction
private function vUnitSuite_assertFalseViolated_test takes VUnitSuite suite returns nothing
call suite.assertFalse(true, "")
endfunction
private function vUnitSuite_assertFalseViolated takes VUnitSuite suite returns nothing
local VUnitSuite s = VUnitSuite.createSilent()
call s.addTest(vUnitSuite_assertFalseViolated_test, "")
call suite.assertFalse(s.run(), "run() on VUnitSuite containing a test violating an assertFalse assertion should return false")
call s.destroy()
endfunction
private function vUnitSuite_assertBooleanEqualsViolated_test takes VUnitSuite suite returns nothing
call suite.assertBooleanEquals(false, true, "")
endfunction
private function vUnitSuite_assertBooleanEqualsViolated takes VUnitSuite suite returns nothing
local VUnitSuite s = VUnitSuite.createSilent()
call s.addTest(vUnitSuite_assertBooleanEqualsViolated_test, "")
call suite.assertFalse(s.run(), "run() on VUnitSuite containing a test violating an assertBooleanEquals assertion should return false")
call s.destroy()
endfunction
private function vUnitSuite_assertIntegerEqualsViolated_test takes VUnitSuite suite returns nothing
call suite.assertIntegerEquals(0, 1, "")
endfunction
private function vUnitSuite_assertIntegerEqualsViolated takes VUnitSuite suite returns nothing
local VUnitSuite s = VUnitSuite.createSilent()
call s.addTest(vUnitSuite_assertIntegerEqualsViolated_test, "")
call suite.assertFalse(s.run(), "run() on VUnitSuite containing a test violating an assertIntegerEquals assertion should return false")
call s.destroy()
endfunction
private function vUnitSuite_assertRealEqualsViolated_test takes VUnitSuite suite returns nothing
call suite.assertRealEquals(0., .125, .1, "")
endfunction
private function vUnitSuite_assertRealEqualsViolated takes VUnitSuite suite returns nothing
local VUnitSuite s = VUnitSuite.createSilent()
call s.addTest(vUnitSuite_assertRealEqualsViolated_test, "")
call suite.assertFalse(s.run(), "run() on VUnitSuite containing a test violating an assertRealEquals assertion should return false")
call s.destroy()
endfunction
private function vUnitSuite_assertStringEqualsViolated_test takes VUnitSuite suite returns nothing
call suite.assertStringEquals("abc", "aBc", "")
endfunction
private function vUnitSuite_assertStringEqualsViolated takes VUnitSuite suite returns nothing
local VUnitSuite s = VUnitSuite.createSilent()
call s.addTest(vUnitSuite_assertStringEqualsViolated_test, "")
call suite.assertFalse(s.run(), "run() on VUnitSuite containing a test violating an assertStringEquals assertion should return false")
call s.destroy()
endfunction
private function ini takes nothing returns nothing
local VUnitSuite vUnitSuite = VUnitSuite.create("VUnitSuite")
call vUnitSuite.addInitializer(vUnitSuite_init)
call vUnitSuite.addTest(vUnitSuite_run, "run")
call vUnitSuite.addTest(vUnitSuite_initterm, "initterm")
call vUnitSuite.addTest(vUnitSuite_assertSatisfied, "assertSatisfied")
call vUnitSuite.addTest(vUnitSuite_failViolated, "failViolated")
call vUnitSuite.addTest(vUnitSuite_assertTrueViolated, "assertTrueViolated")
call vUnitSuite.addTest(vUnitSuite_assertFalseViolated, "assertFalseViolated")
call vUnitSuite.addTest(vUnitSuite_assertBooleanEqualsViolated, "assertBooleanEqualsViolated")
call vUnitSuite.addTest(vUnitSuite_assertIntegerEqualsViolated, "assertIntegerEqualsViolated")
call vUnitSuite.addTest(vUnitSuite_assertRealEqualsViolated, "assertRealEqualsViolated")
call vUnitSuite.addTest(vUnitSuite_assertStringEqualsViolated, "assertStringEqualsViolated")
call TriggerSleepAction(0)
set mother = vUnitSuite
call vUnitSuite.run()
call vUnitSuite.destroy()
endfunction
endlibrary
http://krainert.com/content/vunit/