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 hasas_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 ;