[Gambas-user] New component gb.test

Adrien Prokopowicz adrien.prokopowicz at gmail.com
Tue May 22 16:51:58 CEST 2018


Le 22/05/2018 à 10:57, Christof Thalhofer a écrit :
> We should talk about the way how the code should be executed. Maybe we
> need some magic here. Can (or should) we hand out the code to a separate
> interpreter process? How?

Here's a flowchart of how I (ideally) view the different internal parts 
interacting:

                                   +-----------+
+--------------+      Starts      |           |
|              | +--------------> |           |
|  CI System   |                  |  gbtest3  | <--> CLI User
|  Gambas IDE  |                  |           |
|              | <--------------+ |           |
+--------------+   Test Results   +-----------+
                    (TAP stream)
                                      +     ^
                                      |     |  Returns test result
                     Starts processes |     |  (Exit code)
                        (gbx3 -s …)   |     |  (STDERR for errors)
                                      v     +

                                +------------------+
                                |-------------------+
                                ||                  |
                                ||   Test Methods   |
                                ||    (gb.test)     |
                                ++                  |
                                 +------------------+

(The separation of gbtest3 aside, this flow is quite similar to Tobi's 
implementation, but with some notable differences I'll cover here)

The needed magic resides in the "-s" flag of the interpreter (gbx3), 
which sets a new startup class for running the project (different from 
the MMain/FMain class set by the user).

We can set this flag to an hidden class exported by gb.test (for example 
_TestRunner), which will run its Main() method directly, but in the 
scope of the user's project. :-)

 From there, this hidden class can do two different things (dictated by 
CLI arguments, sent by gbtest3).

First, walk through every class file in the hidden ".gambas" directory, 
keep only the classes that inherit from TestModule/TestContainer (name 
still up to bikeshedding, I prefer the first personally), then list all 
the callable test methods. Then this list of tests is sent back to 
gbtest3 to make the test plan before any test is run.
While this plan is not strictly necessary for the TAP stream (but still 
nice to have, according to the spec), it enables gbtest3 to efficiently 
split the testing work across multiple process running in parallel. It 
also enables the IDE (and external CI tools maybe) to show a nice 
progress bar while the user waits for their tests to run, since the TAP 
output is streamed and not buffered.

Then, when the plan is set up, the gbtest3 executable will start a new 
runner process, instructing it to run a single test and return the 
result (and repeat for evey test in the plan).
Here Tobi's implementation communicates with the test harness (here 
gbtest3) using TAP as well, but since in this model a process lives long 
enough to only run a single test, all TAP outputs would have only one 
results, essentially making TAP useless for this case, which is why I 
rather chose standard exit codes for status (ok/not ok/segfault/etc.), 
and looking through STDERR for the error message (if any).
It also makes the implementation of gb.test much lighter, which is 
always a plus. :-)

This whole model however, has an idea different from the one used by 
Perl (which inspired TAP) and implemented by Tobi here, and much closer 
the ones I see used day-to-day in production at work (whatever the 
language), which I believe is why there were some misunderstandings in 
the previous messages.

It is based on the fact that, semantically, an Assertion is not a just a 
test or check, but a strong and obvious affirmation about how the tested 
system works.
Assertions that succeed are simply ignored and do nothing (i.e. they do 
not show up in any log whatsoever), but if an assertion happens to fail, 
it aborts the current test (or throws an error, in Gambas terminology), 
therefore not running any subsequent assertions or code, and immediately 
marking the test as failed.

A few examples of this model can be found in JUnit's Assert class[0] or 
in Rust's assert! macro[1] (among many others, those are just the ones 
that come to my mind right now).

I believe this is better than Perl's approach of running and logging 
every assertion, because assertions are supposed to be very lightweight 
instructions (in the "good" case at least, where they succeed), which 
enables you to put a lot of them whithout slowing down your test suite 
nor spamming your test log (like in long loops, or testing big chunks of 
data).
And also because when testing, you don't care to the assertions that 
succeeded, you only care about the ones that failed. :-)

(Note this is only about assertions, all test methods that ran are of 
course logged in the TAP output).

> What we also should talk about is, how a couple of tests could be
> arranged (organized) in GUI and/or code.

The GUI should be completely decorrelated to how everything works 
internally and in code (which is what Benoît's initial remarks were 
about), so its presentation shouldn't be a concern right now.

[0] http://junit.sourceforge.net/javadoc/org/junit/Assert.html
[1] https://doc.rust-lang.org/std/macro.assert.html

-- 
Adrien Prokopowicz


More information about the User mailing list