A vJass unit testing library

krainert

Member
Reaction score
10
Does one exist?
If I were to make one, what should I keep in mind?
EDIT: One thing I can't immediately figure out how to do it terminate the execution of a test method once an assert within it fails. Is there an elegant solution, or will I have to go with something like
JASS:
if (assertNotNull(hurfdurf)) then
    return
endif

?
I'm considering using jUnit as my inspirational base, but I should probably introduce some adjustments to fine-tune the system for vJass's personality.
 

krainert

Member
Reaction score
10
Okay, here is a first attempt:
JASS:
library VUnit

//********************************************************************************************************************************
//* VUnit
//*  
//*  ABOUT
//*   Author:   krainert
//*   Version:  1.0
//*   
//*  DESCRIPTION
//*   
//*   
//*  CLASSES
//*   ======== Vector2D ========
//*    -- Description
//*     
//*    -- Static methods
//*     
//*    -- Methods
//*     
//*     
//*  CHANGELOG
//*   1.0  YYYY-MM-DD  Original release
//*   
//********************************************************************************************************************************

globals
    private constant integer MAX_SUITES = 8191
    private constant integer NULL_SUITE = -1
endglobals

function interface VUnitInitializer takes nothing returns nothing
function interface VUnitTerminator  takes nothing returns nothing
function interface VUnitTest        takes nothing returns nothing

struct VUnitSuite[MAX_SUITES]
    private static thistype currentSuite = NULL_SUITE
    private static boolean  success
    
    private string    name
    private integer   initCount = 0
    private integer   termCount = 0
    private integer   testCount = 0
    private hashtable tests     = InitHashtable()
    private hashtable inits     = InitHashtable()
    private hashtable terms     = InitHashtable()
    
    static method create takes string name returns thistype
        local thistype r = thistype.allocate()
        set r.name = name
        return r
    endmethod
    
    private static method fail takes string violation returns nothing
        set thistype.success = false
        call BJDebugMsg(violation)
    endmethod
    
    method addInitializer takes VUnitInitializer init returns nothing
        call SaveInteger(.inits, .initCount, 0, init)
        set .initCount = .initCount + 1
    endmethod
    
    method addTerminatr 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 nothing
        local boolean success = true
        local integer i = 0
        local integer j
        local VUnitInitializer init
        local VUnitTerminator  term
        local VUnitTest        test
        local string           name
        call BJDebugMsg("[VUnit] Suite: " + .name + " (" + I2S(.testCount) + " tests)")
        set thistype.currentSuite = this
        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)
            call BJDebugMsg("[VUnit] Test: " + name)
            set thistype.success = true
            call test.evaluate()
            if (thistype.success) then
                call BJDebugMsg("[VUnit] Test passed")
            else
                set success = false
                call BJDebugMsg("[VUnit] Test failed")
            endif
            
            //terminators
            set j = 0
            loop
                exitwhen j == .initCount
                set init = LoadInteger(.inits, j, 0)
                call init.evaluate()
                set j = j + 1
            endloop
            
            set i = i + 1
        endloop
        set thistype.currentSuite = NULL_SUITE
        if (success) then
            call BJDebugMsg("[VUnit] Suite passed")
        else
            call BJDebugMsg("[VUnit] Suite failed")
        endif
    endmethod
    
    static method assertTrue takes boolean bool returns boolean
        if (bool) then
            return true
        endif
        call thistype.fail("[VUnit] assertTrue violated")
        return false
    endmethod
    
    static method assertIntegerEquals takes integer expected, integer actual returns boolean
        if (expected == actual) then
            return true
        endif
        call thistype.fail("[VUnit] assertIntegerEquals violated; expected: " + I2S(expected) + "; actual: " + I2S(actual))
        return false
    endmethod
endstruct

endlibrary


The following is some simple test code to be put in a trigger named "TestVUnit":

JASS:
library TestVUnit requires VUnit

function TestVUnit_testGenericIntegerEquals takes nothing returns nothing
    call VUnitSuite.assertIntegerEquals(42, 42)
endfunction
function TestVUnit_testGenericIntegerEquals2 takes nothing returns nothing
    call VUnitSuite.assertIntegerEquals(42, 41)
endfunction
function TestVUnit_testGenericIntegerEquals3 takes nothing returns nothing
    call VUnitSuite.assertIntegerEquals(41, 41)
endfunction

function InitTrig_TestVUnit takes nothing returns nothing
    local VUnitSuite suite = VUnitSuite.create("Generic VUnitSuite")
    call suite.addTest(TestVUnit_testGenericIntegerEquals, "42==42")
    call suite.addTest(TestVUnit_testGenericIntegerEquals2, "41==42")
    call suite.addTest(TestVUnit_testGenericIntegerEquals3, "41==41")
    call suite.run()
endfunction

endlibrary


It seems to work thus far :)

Comments? Suggestions? Anything?
 

krainert

Member
Reaction score
10
JASS:
library VUnit

//********************************************************************************************************************************
//* VUnit
//*  
//*  ABOUT
//*   Author:   krainert
//*   Version:  1.0
//*   
//*  DESCRIPTION
//*   A jUnit inspired unit testing framework
//*   
//*  CLASSES
//*   ======== VUnitSuite ========
//*    -- Description
//*     A runnable test suite
//*    -- Static methods
//*     static VUnitSuite create(string name)                                                           - instantiates a new test suite named name
//*     static boolean    assertTrue(boolean condition, string description)                             - asserts condition==true with description description and returns whether the assertion holds
//*     static boolean    assertFalse(boolean condition, string description)                            - asserts condition==false with description description and returns whether the assertion holds
//*     static boolean    assertBooleanEquals(boolean expected, boolean actual, string description)     - asserts actual==expected with description description and returns whether the assertion holds
//*     static boolean    assertIntegerEquals(integer expected, integer actual, string description)     - asserts actual==expected with description description and returns whether the assertion holds
//*     static boolean    assertRealEquals(real expected, real actual, real delta, string description)  - asserts actual==expected with a precision of delta and description description and returns whether the assertion holds
//*     static boolean    assertStringEquals(string expected, string actual, string description)        - asserts actual==expected with description description and returns whether the assertion holds
//*     static boolean    assertNull(integer value, string description)                                 - asserts value==null with description description and returns whether the assertion holds
//*     static boolean    assertNotNull(integer value, string description)                              - asserts value!=null with description description and returns whether the assertion holds
//*    -- Methods
//*     void addInitializer(VUnitInitializer init)                                                      - adds initializer init
//*     void addTerminator(VUnitTerminator term)                                                        - adds terminator term
//*     void addTest(VUnitTest test, string name)                                                       - adds test test named name
//*     void run()                                                                                      - runs all tests
//*     
//*  FUNCTION INTERFACES
//*   void VUnitInitializer()
//*   void VUnitTerminator()
//*   void VUnitTest()
//*   
//*  CHANGELOG
//*   1.0  YYYY-MM-DD  Original release
//*   
//********************************************************************************************************************************

globals
    private constant integer MAX_SUITES = 8191
    private constant integer NULL_SUITE = -1
endglobals

function interface VUnitInitializer takes nothing returns nothing
function interface VUnitTerminator  takes nothing returns nothing
function interface VUnitTest        takes nothing returns nothing

struct VUnitSuite[MAX_SUITES]
    private static thistype currentSuite = NULL_SUITE
    private static boolean  success
    
    private string    name
    private integer   initCount = 0
    private integer   termCount = 0
    private integer   testCount = 0
    private hashtable inits     = InitHashtable()
    private hashtable terms     = InitHashtable()
    private hashtable tests     = InitHashtable()
    
    static method create takes string name returns thistype
        local thistype r = thistype.allocate()
        set r.name = name
        return r
    endmethod
    
    method onDestroy takes nothing returns nothing
        call FlushParentHashtable(.inits)
        call FlushParentHashtable(.terms)
        call FlushParentHashtable(.tests)
    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 nothing
        local boolean success = true
        local integer i = 0
        local integer j
        local VUnitInitializer init
        local VUnitTerminator  term
        local VUnitTest        test
        local string           name
        call BJDebugMsg("[VUnit] Suite: " + .name + " (" + I2S(.testCount) + " tests)")
        set thistype.currentSuite = this
        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)
            call BJDebugMsg("[VUnit] Test: " + name)
            set thistype.success = true
            call test.evaluate()
            if (thistype.success) then
                call BJDebugMsg("[VUnit] Test " + name + " passed")
            else
                set success = false
                call BJDebugMsg("[VUnit] Test " + name + " failed")
            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 thistype.currentSuite = NULL_SUITE
        if (success) then
            call BJDebugMsg("[VUnit] Suite " + .name + " passed")
        else
            call BJDebugMsg("[VUnit] Suite " + .name + " failed")
        endif
    endmethod
    
    private static method assert takes boolean condition, string name, string description returns boolean
        call BJDebugMsg("[VUnit] Assertion: " + name + " (" + description + ")")
        if (condition) then
            call BJDebugMsg("[VUnit] Assertion " + name + " satisfied")
            return true
        else
            set thistype.success = false
            call BJDebugMsg("[VUnit] Assertion " + name + " violated")
            return false
        endif
    endmethod
    
    private static method B2S takes boolean bool returns string
        if (bool) then
            return "true"
        else
            return "false"
        endif
    endmethod
    
    static method assertTrue takes boolean condition, string description returns boolean
        return thistype.assert(condition, "assertTrue(" + B2S(condition) + ")", description)
    endmethod
    
    static method assertFalse takes boolean condition, string description  returns boolean
        return thistype.assert(not(condition), "assertFalse(" + B2S(condition) + ")", description)
    endmethod
    
    static method assertBooleanEquals takes boolean expected, boolean actual, string description returns boolean
        return thistype.assert(actual == expected, "assertBooleanEquals(" + B2S(expected) + "," + B2S(actual) + ")", description)
    endmethod
    
    static method assertIntegerEquals takes integer expected, integer actual, string description returns boolean
        return thistype.assert(actual == expected, "assertIntegerEquals(" + I2S(expected) + "," + I2S(actual) + ")", description)
    endmethod
    
    static method assertRealEquals takes real expected, real actual, real delta, string description returns boolean
        return thistype.assert((actual >= expected - delta) and (actual <= expected + delta), "assertRealEquals(" + R2S(expected) + "," + R2S(actual) + "," + R2S(delta) + ")", description)
    endmethod
    
    static method assertStringEquals takes string expected, string actual, string description returns boolean
        return thistype.assert(actual == expected, "assertRealEquals(" + expected + "," + actual + ")", description)
    endmethod
    
    static method assertNull takes integer value, string description returns boolean
        return thistype.assert(value == 0, "assertNull(" + I2S(value) + ")", description)
    endmethod
    
    static method assertNotNull takes integer value, string description returns boolean
        return thistype.assert(value != 0, "assertNotNull(" + I2S(value) + ")", description)
    endmethod
endstruct

endlibrary


Testing the newest (unreleased) version of Vectors:

JASS:
library VectorsVUnit initializer VectorsVUnit_init requires Vectors, VUnit

private function vector2DCreateXY takes nothing returns nothing
    local Vector2D v = Vector2D.createXY(3., 7.)
    call VUnitSuite.assertRealEquals(3., v.getX(), .001, "x")
    call VUnitSuite.assertRealEquals(7., v.getY(), .001, "y")
    call v.destroy()
endfunction

private function vector2DCreate takes nothing returns nothing
    local Vector2D v1 = Vector2D.createXY(3., 7.)
    local Vector2D v2 = Vector2D.create(v1)
    call VUnitSuite.assertRealEquals(3., v2.getX(), .001, "x")
    call VUnitSuite.assertRealEquals(7., v2.getY(), .001, "y")
    call v1.destroy()
    call v2.destroy()
endfunction

private function vector2DCreateDM takes nothing returns nothing
    local Vector2D v = Vector2D.createDM(.5*3.1416, 2.)
    call VUnitSuite.assertRealEquals(0., v.getX(), .001, "x")
    call VUnitSuite.assertRealEquals(2., v.getY(), .001, "y")
    call v.destroy()
endfunction

private function vector3DCreateXYZ takes nothing returns nothing
    local Vector3D v = Vector3D.createXYZ(3., 7., 4.)
    call VUnitSuite.assertRealEquals(3., v.getX(), .001, "x")
    call VUnitSuite.assertRealEquals(7., v.getY(), .001, "y")
    call VUnitSuite.assertRealEquals(4., v.getZ(), .001, "z")
    call v.destroy()
endfunction

private function vector3DCreate takes nothing returns nothing
    local Vector3D v1 = Vector3D.createXYZ(3., 7., 4.)
    local Vector3D v2 = Vector3D.create(v1)
    call VUnitSuite.assertRealEquals(3., v2.getX(), .001, "x")
    call VUnitSuite.assertRealEquals(7., v2.getY(), .001, "y")
    call VUnitSuite.assertRealEquals(4., v2.getZ(), .001, "z")
    call v1.destroy()
    call v2.destroy()
endfunction

function VectorsVUnit_init takes nothing returns nothing
    local VUnitSuite vector2D = VUnitSuite.create("Vector2D")
    local VUnitSuite vector3D = VUnitSuite.create("Vector3D")
    
    call vector2D.addTest(vector2DCreateXY, "static createXY")
    call vector2D.addTest(vector2DCreate, "static create")
    call vector2D.addTest(vector2DCreateDM, "static createDM")
    
    call vector3D.addTest(vector3DCreateXYZ, "static createXYZ")
    call vector3D.addTest(vector3DCreate, "static create")
    
    call vector2D.run()
    call vector3D.run()
endfunction

endlibrary


Simple and easily maintainable. It liberates the coder from doing too much boiler-plate code while testing while rigidly enforcing a standardized structure for unit tests.

EDIT: Now I have to figure out a sensible way to output VUnit results...
 
General chit-chat
Help Users
  • No one is chatting at the moment.
  • Ghan Ghan:
    Howdy
  • Ghan Ghan:
    Still lurking
    +3
  • The Helper The Helper:
    I am great and it is fantastic to see you my friend!
    +1
  • The Helper The Helper:
    If you are new to the site please check out the Recipe and Food Forum https://www.thehelper.net/forums/recipes-and-food.220/
  • Monovertex Monovertex:
    How come you're so into recipes lately? Never saw this much interest in this topic in the old days of TH.net
  • Monovertex Monovertex:
    Hmm, how do I change my signature?
  • tom_mai78101 tom_mai78101:
    Signatures can be edit in your account profile. As for the old stuffs, I'm thinking it's because Blizzard is now under Microsoft, and because of Microsoft Xbox going the way it is, it's dreadful.
  • The Helper The Helper:
    I am not big on the recipes I am just promoting them - I use the site as a practice place promoting stuff
    +2
  • Monovertex Monovertex:
    @tom_mai78101 I must be blind. If I go on my profile I don't see any area to edit the signature; If I go to account details (settings) I don't see any signature area either.
  • The Helper The Helper:
    You can get there if you click the bell icon (alerts) and choose preferences from the bottom, signature will be in the menu on the left there https://www.thehelper.net/account/preferences
  • The Helper The Helper:
    I think I need to split the Sci/Tech news forum into 2 one for Science and one for Tech but I am hating all the moving of posts I would have to do
  • The Helper The Helper:
    What is up Old Mountain Shadow?
  • The Helper The Helper:
    Happy Thursday!
    +1
  • Varine Varine:
    Crazy how much 3d printing has come in the last few years. Sad that it's not as easily modifiable though
  • Varine Varine:
    I bought an Ender 3 during the pandemic and tinkered with it all the time. Just bought a Sovol, not as easy. I'm trying to make it use a different nozzle because I have a fuck ton of Volcanos, and they use what is basically a modified volcano that is just a smidge longer, and almost every part on this thing needs to be redone to make it work
  • Varine Varine:
    Luckily I have a 3d printer for that, I guess. But it's ridiculous. The regular volcanos are 21mm, these Sovol versions are about 23.5mm
  • Varine Varine:
    So, 2.5mm longer. But the thing that measures the bed is about 1.5mm above the nozzle, so if I swap it with a volcano then I'm 1mm behind it. So cool, new bracket to swap that, but THEN the fan shroud to direct air at the part is ALSO going to be .5mm to low, and so I need to redo that, but by doing that it is a little bit off where it should be blowing and it's throwing it at the heating block instead of the part, and fuck man
  • Varine Varine:
    I didn't realize they designed this entire thing to NOT be modded. I would have just got a fucking Bambu if I knew that, the whole point was I could fuck with this. And no one else makes shit for Sovol so I have to go through them, and they have... interesting pricing models. So I have a new extruder altogether that I'm taking apart and going to just design a whole new one to use my nozzles. Dumb design.
  • Varine Varine:
    Can't just buy a new heatblock, you need to get a whole hotend - so block, heater cartridge, thermistor, heatbreak, and nozzle. And they put this fucking paste in there so I can't take the thermistor or cartridge out with any ease, that's 30 dollars. Or you can get the whole extrudor with the direct driver AND that heatblock for like 50, but you still can't get any of it to come apart
  • Varine Varine:
    Partsbuilt has individual parts I found but they're expensive. I think I can get bits swapped around and make this work with generic shit though
  • Ghan Ghan:
    Heard Houston got hit pretty bad by storms last night. Hope all is well with TH.
  • The Helper The Helper:
    Power back on finally - all is good here no damage
    +2
  • V-SNES V-SNES:
    Happy Friday!
    +1

      The Helper Discord

      Members online

      No members online now.

      Affiliates

      Hive Workshop NUON Dome World Editor Tutorials

      Network Sponsors

      Apex Steel Pipe - Buys and sells Steel Pipe.
      Top