Added more complex inspection of arguments. Changed assert to check.

This commit is contained in:
Joey Payne 2015-07-10 00:00:20 -06:00
commit 9451bfcbf5
4 changed files with 185 additions and 114 deletions

118
README.md
View file

@ -51,24 +51,24 @@ testSuite SuiteName of TestSuite:
method testAddingString()= method testAddingString()=
## adds a string to the suiteVar ## adds a string to the suiteVar
self.suiteVar &= " 123" self.suiteVar &= " 123"
self.assert(self.suiteVar == "Testing 123") r self.check(self.suiteVar == "Testing 123")
proc raisesOs()= proc raisesOs()=
# This proc won't be invoked as a test, it must begin with "test" in lowercase # This proc won't be invoked as a test, it must begin with "test" in lowercase
raise newException(OSError, "Oh no! OS malfunction!") raise newException(OSError, "Oh no! OS malfunction!")
method testRaises()= method testRaises()=
# Two ways of asserting # Two ways of checking
self.assertRaises OSError: self.checkRaises OSError:
self.raisesOs() self.raisesOs()
self.assertRaises(OSError, self.raisesOs()) self.checkRaises(OSError, self.raisesOs())
method testTestObj()= method testTestObj()=
self.assert(self.testObj == 90) self.check(self.testObj == 90)
method testMoreMore()= method testMoreMore()=
self.assert("String" == "String") self.check("String" == "String")
when isMainModule: when isMainModule:
einheit.runTests() einheit.runTests()
@ -86,74 +86,102 @@ nim c -r test.nim
is this: is this:
``` ```
[Running] UnitTests [Running] UnitTests -----------------------------------------------------------
[OK] testForB [OK] testForB
[Failed] testArrayAssert [Failed] testArrayAssert
Condition: assert(self.testArray == [0, 1, 2]) Condition: check(self.testArray == [0, 1, 2])
Reason: self.testArray == [0, 1, 2, 3] Where:
Location: einheit.nim; line 160 self.testArray -> [0, 1, 2, 3]
Location: test.nim; line 27
[Failed] testForC [Failed] testForC
Condition: assert(c == 1) Condition: check(c == 1)
Reason: c == 0 Where:
Location: einheit.nim; line 160 c -> 0
Location: test.nim; line 32
[1/3] tests passed.
[Running] UnitTestsNew [1/3] tests passed for UnitTests. ----------------------------------------------
[Running] UnitTestsNew --------------------------------------------------------
[OK] testTestObj [OK] testTestObj
[OK] testStuff [OK] testStuff
[Failed] testMore [Failed] testMore
Condition: assert(more == 1) Condition: check(more == 1)
Reason: more == 23 Where:
Location: einheit.nim; line 160 more -> 23
Location: test.nim; line 56
[Failed] testMoreMore [Failed] testMoreMore
Condition: assert(self.returnTrue()) Condition: check(self.returnTrue())
Reason: self.returnTrue() == false Where:
Location: einheit.nim; line 160 self.returnTrue() -> false
Location: test.nim; line 59
[2/4] tests passed.
[Running] TestInherit [2/4] tests passed for UnitTestsNew. -------------------------------------------
[Running] TestInherit ---------------------------------------------------------
[OK] testTestObj [OK] testTestObj
[OK] testStuff [OK] testStuff
[Failed] testMore [Failed] testMore
Condition: assert(more == 1) Condition: check(more == 1)
Reason: more == 23 Where:
Location: einheit.nim; line 160 more -> 23
Location: test.nim; line 56
[Failed] testMoreMore [Failed] testMoreMore
Condition: assert(self.returnTrue()) Condition: check(self.returnTrue())
Reason: self.returnTrue() == false Where:
Location: einheit.nim; line 160 self.returnTrue() -> false
[OK] testRaises Location: test.nim; line 59
[3/5] tests passed. [Failed] testRaises
Condition: checkRaises(self.raisesOs())
Where:
self.raisesOs() -> not equal to OSError
Location: test.nim; line 72
[Running] MoreInheritance [2/5] tests passed for TestInherit. --------------------------------------------
[Running] MoreInheritance -----------------------------------------------------
[Failed] testTestObj [Failed] testTestObj
Condition: assert(self.testObj == 90) Condition: check(self.testObj == 90)
Reason: self.testObj == 12345 Where:
Location: einheit.nim; line 160 self.testObj -> 12345
Location: test.nim; line 46
[OK] testStuff [OK] testStuff
[Failed] testMore [Failed] testMore
Condition: assert(more == 1) Condition: check(more == 1)
Reason: more == 23 Where:
Location: einheit.nim; line 160 more -> 23
Location: test.nim; line 56
[Failed] testMoreMore [Failed] testMoreMore
Condition: assert(self.returnTrue()) Condition: check(self.returnTrue())
Reason: self.returnTrue() == false Where:
Location: einheit.nim; line 160 self.returnTrue() -> false
[OK] testRaises Location: test.nim; line 59
[Failed] testRaises
Condition: checkRaises(self.raisesOs())
Where:
self.raisesOs() -> not equal to OSError
Location: test.nim; line 72
[OK] testTestObj [OK] testTestObj
[OK] testNewObj [OK] testNewObj
[4/7] tests passed. [3/7] tests passed for MoreInheritance. ----------------------------------------
``` ```
Notice that on failure, the test runner gives some useful information about the test in question. This is useful for determining why the test failed. Notice that on failure, the test runner gives some useful information about the test in question. This is useful for determining why the test failed.

View file

@ -9,13 +9,13 @@
## ##
## testSuite UnitTests: ## testSuite UnitTests:
## proc thisIsATest()= ## proc thisIsATest()=
## self.assertEquals(1, 1) ## self.check(1 == 1)
## self.assertTrue(2 != 3) ## self.checkRaises(OSError, newException(OSError, "OS is exploding!"))
## self.assertFalse(3 == 4)
## self.assertRaises(OSError, newException(OSError, "OS is exploding!"))
## ##
import macros import macros
import strutils import strutils
import tables
when not defined(ECMAScript): when not defined(ECMAScript):
import terminal import terminal
@ -46,7 +46,7 @@ type
numTests: int numTests: int
TestAssertError = object of Exception TestAssertError = object of Exception
## assertTrue and other assert_* statements will raise ## checkTrue and other check_* statements will raise
## this exception when the condition fails ## this exception when the condition fails
lineNumber: int lineNumber: int
fileName: string fileName: string
@ -69,17 +69,20 @@ method runTests*(suite: TestSuite)=
# ------------------------------------ # ------------------------------------
template returnException(name, testName, snip, vals, pos, posRel)=
template returnException(name, testName, snip, vals)=
## private template for raising an exception ## private template for raising an exception
let pos = instantiationInfo(fullpaths=true)
let posRel = instantiationInfo()
var var
filename = posRel.filename filename = posRel.filename
line = pos.line line = pos.line
var message = "\n" var message = "\n"
message &= " Condition: $2($1)\n".format(snip.replace("\n","").strip(), name) message &= " Condition: $2($1)\n".format(snip.replace("\n","").strip(), name)
try:
message &= " Where:\n"
for k, v in vals.pairs:
message &= " $1 -> $2\n".format(k, v)
except:
message &= " Reason: $1\n".format(vals) message &= " Reason: $1\n".format(vals)
message &= " Location: $1; line $2".format(filename, line) message &= " Location: $1; line $2".format(filename, line)
var exc = newException(TestAssertError, message) var exc = newException(TestAssertError, message)
@ -89,20 +92,24 @@ template returnException(name, testName, snip, vals)=
exc.testName = testName exc.testName = testName
raise exc raise exc
# ------------------------ Templates for assertion ---------------------------- # ------------------------ Templates for checkion ----------------------------
template assertRaises*(self: TestSuite, error: Exception, template checkRaises*(self: TestSuite, error: Exception,
code: untyped): untyped {.immediate.}= code: untyped): untyped {.immediate.}=
## Raises a TestAssertError when the exception "error" is ## Raises a TestAssertError when the exception "error" is
## not thrown in the code ## not thrown in the code
let
pos = instantiationInfo(fullpaths=true)
posRel = instantiationInfo()
try: try:
code code
var var
snip = astToStr(code) snip = astToStr(code).strip()
vals = "No Exception Raised" vals = {snip: "No Exception Raised"}.toTable()
testName = self.currentTestName testName = self.currentTestName
returnException("assertRaises", testName, snip, vals) returnException("checkRaises", testName, snip, vals, pos, posRel)
except error: except error:
discard discard
@ -110,54 +117,76 @@ template assertRaises*(self: TestSuite, error: Exception,
raise raise
except Exception: except Exception:
var var
snip = astToStr(code) snip = astToStr(code).strip()
vals = "Exception != $1".format(astToStr(error)) vals = {snip: "not equal to $1".format(astToStr(error))}.toTable()
testName = self.currentTestName testName = self.currentTestName
returnException("assertRaises", testName, snip, vals) returnException("checkRaises", testName, snip, vals, pos, posRel)
macro isInfix(code: untyped): untyped= template recursive(node, action): expr {.dirty.} =
if code.kind == nnkInfix: proc helper(child: NimNode): NimNode {.gensym.} =
return newLit(true) action
return newLit(false) result = child.copy()
for c in child.children:
if child.kind == nnkCall and c.kind == nnkDotExpr:
continue
result.add helper(c)
discard helper(node)
macro getLhs(code: untyped): untyped= template strRep(n: NimNode): untyped=
return code[1].toStrLit() newNimNode(nnkPrefix).add(ident("$"), newNimNode(nnkPar).add(n))
macro getLhsVal(code: untyped): untyped= template tableEntry(n: NimNode): untyped=
return code[1] newNimNode(nnkExprColonExpr).add(n.toStrLit(), strRep(n))
macro getRhs(code: untyped): untyped= macro getSyms(code:untyped): untyped=
return code[2] var
tableCall = newNimNode(nnkCall).add(ident("toTable"))
tableConstr = newNimNode(nnkTableConstr)
macro getRhsVal(code: untyped): untyped= recursive(code):
return code[2] let ch1 = child
if ch1.kind == nnkInfix:
if child[1].kind == nnkIdent:
tableConstr.add(tableEntry(child[1]))
if child[2].kind == nnkIdent:
tableConstr.add(tableEntry(child[2]))
elif ch1.kind == nnkExprColonExpr:
if child[0].kind == nnkIdent:
tableConstr.add(tableEntry(child[0]))
if child[1].kind == nnkIdent:
tableConstr.add(tableEntry(child[1]))
elif ch1.kind == nnkCall or ch1.kind == nnkCommand:
tableConstr.add(tableEntry(ch1))
for i in 1..<ch1.len():
tableConstr.add(tableEntry(ch1[i]))
elif ch1.kind == nnkDotExpr:
tableConstr.add(tableEntry(ch1))
if tableConstr.len() != 0:
tableCall.add(tableConstr)
result = tableCall
else:
template emptyTable()=
newTable[string, string]()
result = getAst(emptyTable())
template assert*(self: TestSuite, code: untyped){.immediate.}=
template check*(self: TestSuite, code: untyped){.immediate.}=
## Assertions for tests ## Assertions for tests
if not code: if not code:
# These need to be here to capture the actual info
let
pos = instantiationInfo(fullpaths=true)
posRel = instantiationInfo()
var var
snip = "" snip = ""
vals = ""
testName = self.currentTestName testName = self.currentTestName
when isInfix(code) == 0: var vals = getSyms(code)
snip = astToStr(code) snip = astToStr(code)
vals = "$1 == $2".format(snip, code)
else:
snip = astToStr(code)
var
lhs = getLhs(code)
lhsVal = getLhsVal(code)
rhs = getRhs(code)
rhsVal = getRhsVal(code)
if $lhs != $lhsVal:
vals &= "$1 == $2".format(lhs, lhsVal)
if $rhs != $rhsVal:
vals &= "$1 == $2".format(rhs, rhsVal)
returnException("assert", testName, snip, vals) returnException("check", testName, snip, vals, pos, posRel)
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
@ -202,7 +231,7 @@ macro testSuite*(head: untyped, body: untyped): untyped =
## method testAddingString()= ## method testAddingString()=
## ## adds a string to the suiteVar ## ## adds a string to the suiteVar
## self.suiteVar &= " 123" ## self.suiteVar &= " 123"
## self.assertEqual(self.suiteVar, "Testing 123") ## self.check(self.suiteVar == "Testing 123")
## ##
## when isMainModule: ## when isMainModule:
## einheit.runTests() ## einheit.runTests()
@ -216,6 +245,7 @@ macro testSuite*(head: untyped, body: untyped): untyped =
template importRequiredLibs()= template importRequiredLibs()=
import strutils import strutils
import tables
when not defined(ECMAScript): when not defined(ECMAScript):
import terminal import terminal
@ -447,7 +477,7 @@ macro testSuite*(head: untyped, body: untyped): untyped =
let e = (ref TestAssertError)(getCurrentException()) let e = (ref TestAssertError)(getCurrentException())
styledEcho(styleBright, styledEcho(styleBright,
fgRed, "[Failed]", fgRed, "[Failed]",
fgWhite, " ", self.currentTestName, e.msg) fgWhite, " ", self.currentTestName, e.msg, "\n")
runTests[0][6].add(getAst(setTestName(ident(objReference), procName))) runTests[0][6].add(getAst(setTestName(ident(objReference), procName)))
runTests[0][6].add(getAst(tryBlock(ident(objReference), procCall))) runTests[0][6].add(getAst(tryBlock(ident(objReference), procCall)))
@ -550,10 +580,14 @@ proc runTests*()=
## usually inside a "when isMainModule" block ## usually inside a "when isMainModule" block
for suite in testSuites: for suite in testSuites:
suite.setup() suite.setup()
var numTicks = 80 - 12 - len(suite.name)
var ticks = ""
for i in 0..<numTicks:
ticks &= "-"
styledEcho(styleBright, styledEcho(styleBright,
fgYellow, "\n[Running]", fgYellow, "\n[Running]",
fgWhite, " $1\n".format(suite.name)) fgWhite, " $1 ".format(suite.name),
fgYellow, " ", ticks, "\n")
suite.runTests() suite.runTests()
suite.tearDown() suite.tearDown()
@ -562,7 +596,16 @@ proc runTests*()=
if suite.testsPassed != suite.numTests: if suite.testsPassed != suite.numTests:
color = fgRed color = fgRed
styledEcho(styleBright, color, var passedStr = "[" & $suite.testsPassed & "/" & $suite.numTests & "]"
"\n[", $suite.testsPassed, "/", $suite.numTests, "]",
fgWhite, " tests passed.\n") ticks = " "
numTicks = 80 - len(passedStr) - 20 - len(suite.name)
for i in 0..<numTicks:
ticks &= "-"
styledEcho(styleBright, color,
"\n", passedStr,
fgWhite, " tests passed for ", suite.name, ".",
fgYellow, ticks, "\n")

View file

@ -1,6 +1,6 @@
[Package] [Package]
name = "einheit" name = "einheit"
version = "0.1.1" version = "0.1.2"
author = "Joey Payne" author = "Joey Payne"
description = "Tool for providing unit tests. Einheit is German for Unit." description = "Tool for providing unit tests. Einheit is German for Unit."
license = "MIT" license = "MIT"

View file

@ -8,7 +8,7 @@ testSuite UnitTests:
proc doThings()= proc doThings()=
# This proc won't be invoked as a test # This proc won't be invoked as a test
self.testObj = 400 self.testObj = 400
self.assert(self.testObj == 400) self.check(self.testObj == 400)
method setup()= method setup()=
self.testObj = 90 self.testObj = 90
@ -21,15 +21,15 @@ testSuite UnitTests:
method testForB()= method testForB()=
var b = 4 var b = 4
self.doThings() self.doThings()
self.assert(b == 4) self.check(b == 4)
method testArrayAssert()= method testArrayAssert()=
self.assert(self.testArray == [0,1,2]) self.check(self.testArray == [0,1,2])
method testForC()= method testForC()=
var c = 0 var c = 0
# supposed to fail # supposed to fail
self.assert(c == 1) self.check(c == 1)
testSuite UnitTestsNew: testSuite UnitTestsNew:
@ -43,20 +43,20 @@ testSuite UnitTestsNew:
self.testObj = 0 self.testObj = 0
method testTestObj()= method testTestObj()=
self.assert(self.testObj == 90) self.check(self.testObj == 90)
method testStuff()= method testStuff()=
self.assert("Stuff" == "Stuff") self.check("Stuff" == "Stuff")
proc returnTrue(): bool= proc returnTrue(): bool=
return false return false
method testMore()= method testMore()=
var more = 23 var more = 23
self.assert(more == 1) self.check(more == 1)
method testMoreMore()= method testMoreMore()=
self.assert(self.returnTrue()) self.check(self.returnTrue())
# Inheritance! # Inheritance!
testSuite TestInherit of UnitTestsNew: testSuite TestInherit of UnitTestsNew:
@ -64,15 +64,15 @@ testSuite TestInherit of UnitTestsNew:
proc raisesOs()= proc raisesOs()=
# This proc won't be invoked as a test # This proc won't be invoked as a test
raise newException(OSError, "Oh no! OS malfunction!") raise newException(Exception, "Oh no! OS malfunction!")
method testRaises()= method testRaises()=
# Two ways of asserting # Two ways of checking
self.assertRaises OSError: self.checkRaises OSError:
self.raisesOs() self.raisesOs()
self.assertRaises(OSError, self.raisesOs()) self.checkRaises(OSError, self.raisesOs())
testSuite MoreInheritance of TestInherit: testSuite MoreInheritance of TestInherit:
@ -100,10 +100,10 @@ testSuite MoreInheritance of TestInherit:
# However, currently this method will be run # However, currently this method will be run
# IN ADDITION to the base class's method. # IN ADDITION to the base class's method.
# This one will pass, the other will fail # This one will pass, the other will fail
self.assert(self.testObj == 12345) self.check(self.testObj == 12345)
method testNewObj()= method testNewObj()=
self.assert(self.testObj == 12345) self.check(self.testObj == 12345)
when isMainModule: when isMainModule: