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()=
## adds a string to the suiteVar
self.suiteVar &= " 123"
self.assert(self.suiteVar == "Testing 123")
r self.check(self.suiteVar == "Testing 123")
proc raisesOs()=
# This proc won't be invoked as a test, it must begin with "test" in lowercase
raise newException(OSError, "Oh no! OS malfunction!")
method testRaises()=
# Two ways of asserting
self.assertRaises OSError:
# Two ways of checking
self.checkRaises OSError:
self.raisesOs()
self.assertRaises(OSError, self.raisesOs())
self.checkRaises(OSError, self.raisesOs())
method testTestObj()=
self.assert(self.testObj == 90)
self.check(self.testObj == 90)
method testMoreMore()=
self.assert("String" == "String")
self.check("String" == "String")
when isMainModule:
einheit.runTests()
@ -86,74 +86,102 @@ nim c -r test.nim
is this:
```
[Running] UnitTests
[Running] UnitTests -----------------------------------------------------------
[OK] testForB
[Failed] testArrayAssert
Condition: assert(self.testArray == [0, 1, 2])
Reason: self.testArray == [0, 1, 2, 3]
Location: einheit.nim; line 160
Condition: check(self.testArray == [0, 1, 2])
Where:
self.testArray -> [0, 1, 2, 3]
Location: test.nim; line 27
[Failed] testForC
Condition: assert(c == 1)
Reason: c == 0
Location: einheit.nim; line 160
[1/3] tests passed.
Condition: check(c == 1)
Where:
c -> 0
Location: test.nim; line 32
[Running] UnitTestsNew
[1/3] tests passed for UnitTests. ----------------------------------------------
[Running] UnitTestsNew --------------------------------------------------------
[OK] testTestObj
[OK] testStuff
[Failed] testMore
Condition: assert(more == 1)
Reason: more == 23
Location: einheit.nim; line 160
Condition: check(more == 1)
Where:
more -> 23
Location: test.nim; line 56
[Failed] testMoreMore
Condition: assert(self.returnTrue())
Reason: self.returnTrue() == false
Location: einheit.nim; line 160
[2/4] tests passed.
Condition: check(self.returnTrue())
Where:
self.returnTrue() -> false
Location: test.nim; line 59
[Running] TestInherit
[2/4] tests passed for UnitTestsNew. -------------------------------------------
[Running] TestInherit ---------------------------------------------------------
[OK] testTestObj
[OK] testStuff
[Failed] testMore
Condition: assert(more == 1)
Reason: more == 23
Location: einheit.nim; line 160
Condition: check(more == 1)
Where:
more -> 23
Location: test.nim; line 56
[Failed] testMoreMore
Condition: assert(self.returnTrue())
Reason: self.returnTrue() == false
Location: einheit.nim; line 160
[OK] testRaises
Condition: check(self.returnTrue())
Where:
self.returnTrue() -> false
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
Condition: assert(self.testObj == 90)
Reason: self.testObj == 12345
Location: einheit.nim; line 160
Condition: check(self.testObj == 90)
Where:
self.testObj -> 12345
Location: test.nim; line 46
[OK] testStuff
[Failed] testMore
Condition: assert(more == 1)
Reason: more == 23
Location: einheit.nim; line 160
Condition: check(more == 1)
Where:
more -> 23
Location: test.nim; line 56
[Failed] testMoreMore
Condition: assert(self.returnTrue())
Reason: self.returnTrue() == false
Location: einheit.nim; line 160
[OK] testRaises
Condition: check(self.returnTrue())
Where:
self.returnTrue() -> false
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] 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.

View file

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

View file

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