====== package require tclunit ====== Adds these words to the global namespace ... * zource - a single verb to facilitate quickly resetting the test environment. * test - verb. takes a glob pattern which is matched against the names of the defined testsuites. Uses zource. * testsuite - use this in your source file or in a separate file, to describe collections of tests. * s/b - verb. The primary test. Takes 2 arguments. A cmd, which is valid Tcl inside braces. And an expected value. * s/nb - the negated version of s/b. Sample interaction. ====== $ source mything.test mything-test $ test * ============================================== testsuite 'cannon' : results : 11/11 : 100% ============================================== testsuite 'cursor' : results : 11/11 : 100% ============================================== testsuite 'makes' : results : 1/1 : 100% ======================================================== testsuite 'namespacequestions' : results : 1/1 : 100% ======================================================== testsuite 'rat' : results : 5/5 : 100% ========================================== testsuite 'ratv' : results : 5/5 : 100% =========================================== testsuite 'trans' : results : 2/2 : 100% =========================================== ======================================================== testsuite 'generic' : results : 14/15 : 1 test failed. ======================================================== test 'new' failed s/b [generic::color {kin 8}] ... ...Blue... Yellow ======================================================= testsuite 'command' : results : 7/9 : 2 tests failed. ======================================================= test 'arithmetic' failed s/b [command::+ 3] ... ...nsdate {1966 6 4}... invalid command name "command::+" test 'parse' failed s/b [command::kin 10 25 2014] ... ...... 100 ====== * testsuites organize tests. * the '...' ellipsis is used to delimit the expected result - accenting leading and trailing white space. * the actual result is indented by 3 white space, aligning exactly w/ the ellipsis to facilitate a copy/paste workflow. Example zource: ====== zource { package forget mytestingenvthing source "abs/path/to/file1" source "abs/path/to/file2" source "path/to/this/file" } ====== Called w/ one argument, means "this is my reset script". Called w/ no argument, means "reset my environment". Used to reset/re-source everything necessary for testing. Example testsuite: ====== package require tclunit zource { source "~/path/projectsrc.tcl" source "~/path/projectsrc.test" namespace path ::projectns } testsuite ratv { test ns { s/b {ratv -ns 1 30 12 3} {nsdate {-ns 1 30 12 3}} s/b {ratv NS 1 26 13 16} {nsdate {NS 1 26 13 16}} s/b {ratv NS 1 24 8 20 leapday} {nsdate {NS 1 24 8 20 leapday}} } test execdate { s/b {lindex [ratv {*}[exec date]] 0} {gcdate} s/b {lindex [ratv [exec date]] 0} {gcdate} } -test execdate2 { s/b {ratv {*}[exec date]} {gcdate {2014 7 12}} s/b {ratv [exec date]} {gcdate {2014 7 12}} } } testsuite rat { test ns { s/b {rat {-ns 1 30 12 3}} {nsdate -ns 1 30 12 3} s/b {rat {NS 1 26 13 16}} {nsdate NS 1 26 13 16} } test execdate { s/b {lindex [rat [exec date]] 0} {gcdate} } -test execdate2 { s/b {rat [exec date]} {gcdate 2014 7 12} } } ====== A testsuite has a title and a collection of tests A test has a title and a body (collection of statements) A body is arbitrary tcl code Example s/b. ====== testsuite equations { test quadratic { s/b {quadratic_eq 1 0 -16} {4 -4} s/nb {quadratic_eq 1 0 -16} 4 s/b {quadratic_eq 1 0 -9} {3 -3} } } ====== s/b and s/nb perform the testing, * [eval] 1st phrase in the global scope, generating a result * compare result with 2nd phrase. **tclunit.tcl** ====== # tclunit # a featherweight test harness ############################################################################### namespace eval tclunit { set version 0.3 variable lastsuiteglob "" variable suites variable successcount variable failcount variable testcount variable msglog variable echoonrepeat 1 variable logsuccess 0 variable zourcevalue "" variable zourcestate uninitialized array set suites {} namespace export testsuite s/b s/nb } namespace eval tclunitcmds { namespace export zource test } ############################################################################### # [ zource ] # a proc/parameter. # w/ a value; make an assignment # w/o no value; eval assignment in global namespace ############################################################################### proc tclunitcmds::zource {args} { variable ::tclunit::zourcestate variable ::tclunit::zourcevalue if { [llength $args] == 0 } { if {$zourcestate eq "uninitialized"} { puts "zource uninitialized." return } catch {uplevel #0 $zourcevalue} res return } else { set zourcestate "" set zourcevalue {*}$args } } ############################################################################### # [ test ] # w/o args; resets env and repeats last set of tests # w/ args; globs against the names of currently defined testsuites ############################################################################### proc tclunitcmds::test {args} { zource ::tclunit::testrunner $args } ############################################################################### # [s/b] # cmd is presumed to be a statement in tcl # expected is matched against return value # s/b 'expects' them to match # s/nb 'expects' them to not match ############################################################################### proc tclunit::s/b {cmd expected} { catch {uplevel 1 $cmd} res if {$res != $expected} { return -code error "s/b \[$cmd\] ...\n...$expected...\n $res" } } proc tclunit::s/nb {cmd expected} { catch {uplevel 1 $cmd} res if {$res == $expected} { return -code error "s/nb \[$cmd\] ...\n...$expected...\n $res" } } proc tclunit::test {title body} { incr ::tclunit::testcount if { [catch {uplevel #0 $body} res opts] } { incr ::tclunit::failcount lappend ::tclunit::msglog "test '$title' failed\n$res" } { incr ::tclunit::successcount if { $::tclunit::logsuccess } { lappend ::tclunit::msglog "...success : $res" } } } proc tclunit::-test {title body} { # consumes test, does nothing, use to disable a test } proc tclunit::testsuite {title tests} { # use first testsuite as initial glob value if { $::tclunit::lastsuiteglob eq "" } { set ::tclunit::lastsuiteglob $title } set ::tclunit::suites($title) $tests } proc tclunit::testrunner {suiteglob} { variable ::tclunit::lastsuiteglob variable ::tclunit::suites variable ::tclunit::successcount variable ::tclunit::failcount variable ::tclunit::msglog variable ::tclunit::testcount variable ::tclunit::echoonrepeat if {$suiteglob eq "" || $suiteglob eq {}} { if { ![info exists lastsuiteglob] || $lastsuiteglob eq "" } { puts "yawn." return } set suiteglob $lastsuiteglob if {$echoonrepeat} { puts "testing: $suiteglob" } } set lastsuiteglob $suiteglob set results [list ] set hbar0 "" set q00 "" foreach suite [lsort [array names suites $suiteglob]] { set tests $suites($suite) set msglog "" set failcount 0 set successcount 0 set testcount 0 # run tests foreach {test title body} $tests { eval [list $test $title $body] } # collect results if {$failcount} { lappend results [list $suite $testcount $successcount $failcount $msglog] } { # print succeesses right away. set q0 "testsuite '$suite' : results : $successcount/$testcount : " set q "$q0 100% " set p [string repeat = [string length $q]] if { [string length $p] > [string length $hbar0] } { set hbar0 $p } if { [string length $hbar0] > [string length $q00] } { set hbar0 $p } set q00 $q puts $hbar0 puts $q } } # sort fail results foreach r [lsort -increasing -integer -index 3 $results] { lassign $r suite testcount successcount failcount msglog if { $hbar0 ne "" } { puts $hbar0 set hbar0 "" } set q0 "testsuite '$suite' : results : $successcount/$testcount : " if { $failcount == 1 } { set q2 "test failed." } { set q2 "tests failed." } set q "$q0 $failcount $q2" set p [string repeat = [string length $q]] puts $p puts $q puts $p if { $msglog ne "" } { puts [join $msglog \n] } } if { $hbar0 ne "" } { puts $hbar0 } } proc tclunit::? {} {lsort [info procs ::tclunit::*]} ############################################################################### namespace import -force tclunit::* namespace import -force tclunitcmds::* ############################################################################### package provide tclunit $tclunit::version ############################################################################### ====== <>Testing