Arjen Markus (10 december 2004) Here is a little script that will generate tests with random data. It is far from complete, but it could be a nice start for a more general tool.
Note: One thing I noticed implementing mathematical packages for Tcllib is that it is necessary to use proper integers at times instead of doubles, because that can reveal mistakes in using [expr]. Just a note I wanted to post somewhere ...
See also quickcheck.
# randomtest.tcl -- # A first shot at generating and interpreting random test cases # # Inspired by Andreas Kupries who mentioned <http://arxiv.org/abs/cs.PL/0412012> # namespace eval ::randomtesting { namespace eval work { # Provide the working environment } namespace export randomtest variable error variable precond } # randomtest -- # Provide the framework for generating and interpreting random tests # # Arguments: # title Title for the test case # count Number of cases to generate # code Code to run # # Result: # None # proc randomtesting::randomtest {title count code} { variable error variable precond set error_count 0 for { set i 0 } { $i < $count } { incr i } { set error 0 set precond 0 # # TODO: Should clean the variables in "work"! # TODO: report the failing cases in more detail # namespace eval work $code incr error_count $error } if { $error_count != 0 } { puts "$title: $error_count failures in $count test cases" } } # precond -- # Check the preconditions - so failure can be properly dealt with, # as can non-failure # # Arguments: # cond Precondition to be checked # # Result: # None # proc randomtesting::work::precond {cond} { if { ! [uplevel [list expr $cond]] } { set ::randomtesting::precond 1 } } # postcond -- # Check the postconditions - so results can be properly checked # # Arguments: # cond Postcondition to be checked # # Result: # None # proc randomtesting::work::postcond {cond} { if { ! [uplevel [list expr $cond]] } { set ::randomtesting::error 1 } } # run -- # Run the actual code under test # # Arguments: # code Code to be run # # Result: # None # # Note: # By allowing more than one fragment of code we could generate test # cases that run multiple "methods" in any random order. This is # a TODO ... # proc randomtesting::work::run {code} { set error [catch [list uplevel $code] msg] if { $error != 0 } { # # An error occurred, was this expected? # if { $::randomtesting::precond == 0 } { set ::randomtesting::error 1 puts $msg } } else { # # No error occurred, was this expected? # if { $::randomtesting::precond == 1 } { set ::randomtesting::error 1 puts "Code ran smoothly, despite violation of precondition" } } } # old -- # Return the old value of a variable # # Arguments: # name Name of the variable # # Result: # Old value (before running the code under test) # # Note: # This is a TODO # proc randomtesting::work::old {name} { error "old not implemented yet" } # randomfloat -- # Return a random floating-point number within a given range # # Arguments: # min Minimum value # max Maximum value # # Result: # A random number within the given range # proc randomtesting::work::randomfloat {min max} { expr {($max-$min)*rand()+$min} } # Now a small example: # addlog is supposed to add the logarithms of two numbers, # so its preconditions are x > 0 and y > 0. If these are # met it returns a float value. # proc addlog {x y} { expr {log($x*$y)} ;# More permissive than the description above! } namespace import ::randomtesting::* randomtest "addlog" 100 { set x [randomfloat -100.0 100.0] set y [randomfloat -100.0 100.0] precond { $x > 0 } precond { $y > 0 } # # Make sure the variable result has a value ... TODO set result {} run { set result [addlog $x $y] } postcond { $x <= 0.0 || $y <= 0.0 || [string is double -strict $result] } }