Automatisches Testen zur Ladezeit
Die korrekte Funktionsweise von Wort-Definitionen sollte sichergestellt werden, um robuste und zuverlässige Programme zu bekommen. Traditionell testet der Forth-Programmierer seine Definitionen „Bottom-Up“ unmittelbar nach dem er sie vorgenommen hat.
Um dieses Tests etwa bei Änderungen nicht ständig wiederholen zu müssen, macht es Sinn, sie zu automatisieren und vom Forth-System selbst vornehmen zu lassen. Das ist insbesondere sinnvoll, um die korrekte Funktionsweise von Bibliotheks-Funktionen sicherzustellen, deren Definitionen auf verschiedene - sich ja in Details unterscheidende - Forth-Systeme geladen werden sollen.
In Forth kann man die Ladezeit, die Übersetzungszeit und die Laufzeit unterscheiden. Der hier vorgestellte kleine Test-Rahmen erlaubt das Testen zur Ladezeit.
Nachdem Definitionen vorgenommen werden, etwa das Durchlaufen einer gelinkten Liste mit Aufsummieren der Elemente:
: sum-list ( ’list -- n ) \ calculate the sum N of all elements in list ’LIST 0 SWAP BEGIN ( n l ) ?DUP WHILE ( n l ) DUP CELL+ @ ROT + SWAP @ REPEAT ; ( n )
ist sicherzustellen, dass diese Definition wie erwartet arbeitet. Dazu legt man im Dictonary geeignete Datenstrukturen an, ruft sum-list
auf und überprüft das Resultat. Der Forth-Programmierer macht das meist interaktiv, aber wir wollen das hier zur Ladezeit erledigen und schreiben im Anschluss an die sum-list
-Definition:
Test,_that 0 sum-list has 0 as_result. Test,_that here 0 , 30 , here swap , 20 , here swap , 10 , sum-list has 60 as_result.
Jeder Testfall wird mit Test,_that
eingeleitet (friert den Stack und Dictionary-Zustand ein).
Dann werden Hilfsdefinitionen vorgenommen (die ggf, das Dictionary ändern).
Es folgen Aufrufe der ursprünglichen Definitionen, sum-list
in unserem Beispiel.
Mit has
… as_result.
wird überprüft, dass der Stack die gewünschten Werte enthält
und bei Nichtübereinstimmung ein Fehler ausgegeben. Stimmen berechnete und Sollwerte
auf dem Stack überein, wird der Stack und das Dictionary von allen Testdefinition bereinigt,
so dass nur die ursprünglichen Definitionen übrig bleiben.
Dann kann mit dem Laden des weiteren Programms fortgefahren werden.
Hier also die Implementierung des Test-Rahmens:
\ Test frame \ Test,_that <defs> <calls> has <items> as_result. VARIABLE d0 VARIABLE d1 : #items ( -- n ) d1 @ d0 @ - ; : Test,_that ( i*x -- i*x ) DEPTH d0 ! S" MARKER *TeSt*" EVALUATE ; : has ( i*x -- i*x ) DEPTH d1 ! ; : ?wrong ( f -- ) Abort" Test failed!" ; : as_result. ( d0*x [d1-d0]*x i*x -- d0*x ) DEPTH d1 @ - #items - ?wrong #items 0 ?DO I PICK I #items + 1+ PICK - ?wrong LOOP #items 2* 0 ?DO DROP LOOP S" *TeSt*" EVALUATE ;