Skip to content

Testing

Paul Mansour edited this page Jan 21, 2022 · 10 revisions

The Abacus test framework is a single function RunTests that operates on a namespace containing one or more test functions. Each test function must begin with an uppercase "T" (e.g.: TestClickProcess). The right argument to RunTests is the HTMLRender instance, the optional left argument is the namespace containing the test functions. This defaults to the space from which RunTests is called.

Each test generally involves firing one or more events in the HTMLRenderer under with the FireEventAndWait function, letting APL handle the response, and then verifying that some state has changed as expected. This state can be the HTMLBrowser (the JavaScript DOM), the APLDOM, or some other data in APL, or some combination of all three. The ExecuteOnElementSync function may be used to query the browser state synchronously.

The test function takes the HTMLRenderer as its right argument, and must return the integer 0, 1, 3, or 4 indicating Passed, Failed, Not Applicable, or Disabled respectively. The value 2 is reserved for broken tests, and supplied automatically by the test framework.

NB. Currently there is a threading issue with the HTMLRenderer that requires that the test framework function runs in a thread. It will not work in the main thread (0). This prevents us from easily running the test suite under program control and taking some automated action upon completion of the tests. Hopefully Dyalog will resolve this issue soon. Note further that there is no need to explicitly thread the test framework function, it is handled internally. This threading issue also prevents the test function itself from starting up the HTMLRenderer itself, which is why it must be passed as the left argument to the RunTest function.

The SampleTests Namespace

The SampleTests namespace contains a simple application that demonstrates the test framework. The Build function creates the HTML document, or application page:

Build←{
     New←A.New
     d←A.NewDocument 0
     d.Caption←'Testing Framework Example'
     b←A.GetBody d
     ib←b New'button' 'Increment'
     ib.id←'increment'
     ib.Onclick←A.FQP'OnIncrement'
     db←b New'button' 'Decrement'
     db.id←'decrement'
     db.Onclick←A.FQP'OnDecrement'
     _←b New'p' 'The current value is:'
     r←b New'p'(,'0')
     r.id←'result'
     d
 }

This is a simple page with two buttons for incrementing and decrementing a counter displayed in a paragraph.

The OnIncrement callback function increments the counter by 1:

OnIncrement←{
     h←⍵.HTMLRenderer
     d←h.Document
     r←d A.ElementById'result'
     r A.SetInnerHTML⍕1+⍎⊃r.Content
 }

And the OnDecrement callback function (should) decrement the counter by 1:

OnDecrement←{
     h←⍵.HTMLRenderer
     d←h.Document
     r←d A.ElementById'result'
     r A.SetInnerHTML⍕¯2+⍎⊃r.Content
 }

Note that OnDecrement has an error in it, and actually subtracts 2 rather than 1. Note further that we are storing the state (the value of the counter) as text right in the paragraph element using execute and format to get and put the value. We are not storing a separate numeric value some place else.

The application can be run in the session using the Run function:

Run←{
     ⎕THIS.A←##.Main
     A.NewForm Build 0
 }

This function builds the page and instantiates it in a new HTMLRenderer object, which is returned as the result.

To run the tests, first execute the Run function and assign the result:

      f←Run 0

Then execute the tests:

      ##.Main.RunTests f
TestDecrement: Failed
TestIncrement: Passed
 Passed    1
 Failed    1
 Broken    0
 N/A       0
 Disabled  0
 Total     2 

You must run these two steps separately, one after the other in the session. You cannot write ##.Main.RunTests Run 0. This will hang due to the threading behavior noted above.

The TestIncrement test function passes:

TestIncrement←{
     d←⍵.Document
     r←d A.ElementById'result'
     v←⍎⊃r.Content
     b←d A.ElementById'increment'
     e←b A.FireEventAndWait'click'
     (v+1)≠⍎⊃r.Content:1
     ih←⊃r A.ExecuteOnElementSync'innerHTML'
     (⍕v+1)≢ih:1
     0
 }

Let's look at this function in detail. The argument to the function is the HTMLRenederer object, which knows its associated document, d. This is the APLDOM object created in the Build function. The APLDOM is kept in sync with the HTMLRenderer DOM. We extract the paragraph element r that contains the result, and then convert the content to a number, v. We then extract the increment button from the APLDOM and fire a click event on it in the HTMLRenderer. The causes the HTMLRenderer to send a message back into APL where the OnIncrement function is executed. We then check that the content in the APLDOM has indeed been incremented, and also that the HTML on the page has changed appropriately.

The TestDecrement function is almost identical, but fails as the code decrements by 2 rather than 1.