The framework comprises the class Tester2 which comes with functions designed to manage and execute test cases.
Tester2 cooperates with the class CodeCoverage, which is designed to produce a code coverage report, which is recommended.
Both Tester2 and CodeCoverage can be loaded as Tatin packages.
Tester2 was originally implemented as a framework for testing all the projects of the APLTree library, but became quickly a general solution.
You are likely to find the framework flexible enough to suit your own needs when it comes to implementing tests.
Depending on how the Test framework is used it might or might not present a GUI. Note that the GUI is a Windows-only feature although in all other respects, Tester2 works on all platforms; if you decide to avoid the GUI, messages are printed to the Dyalog session window.
The GUI was mainly introduced for Tester2’s own sake: Tester2 is used to test itself, and when used without the GUI the messages printed to the session are pretty confusing, while the GUI gives you proper feedback even in that scenario.
Note that executing a large test suite with the GUI is significantly slower than doing the same without the GUI.
You are advised to not use the GUI except when you…
Tester2 itselfTester2 can easier be explained by using the GUI, so in this document it is used.
This is how the GUI looks like:

The “Log” tab shows all the messages the Tester2 class provides:
Initial was found and executed, and what it returned, if anythingCleanup was found and executedThe “Details” tab shows a list of all test cases with…
Test_) in the second columnFailed, OK or WindowsOnly etc., in the fourth column, or {Broken} if that test crashed.
The status column shows:
✓ okay* failed# broken⍝ inactiveDifferent statuses show in different colours.
After all test cases got processed, the user might interact with the GUI. These are the commands presented by the context menu:

Note that all Run* functions return a two-element vector:
rc) with 0 for “okay”.rc is 0 and might contain additional information in case rc is not 0.A reference pointing to the GUI is assigned internally. That’s why the GUI does not disappear straight away after all test cases have been executed. To get rid of it either click the “Close” box or call the T.CloseGUI method which does not require an argument.
Note that test cases causing a crash are considered “broken”. Test cases that do not return the expected result are considered “failing”.
The Tester2 class assumes that all your tests are hosted in a namespace. It may be an ordinary (recommended) or a scripted namespace, but it must not be an unnamed namespace.
You must create an instance of the Tester2 class to do anything useful.
The constructor demands a right argument: that must be a reference to the namespace that hosts all your test cases.
Example: given that all your test cases live in Foo, then you could create an instance T with: T←⎕NEW Tester2 Foo
All test functions inside their namespace are expected to start their names with Test_ followed either by some digits or a group name followed by an underscore followed by some digits, for example, Test_Foo_003.
It is recommended that in case you use groups you assign all your test cases to groups.
The number of digits you use for numbering is not restricted: Test_foo_1 is fine, and so is Test_foo_0000001. However, they should be consistent, at least within a group.
In each test function, the first line after the header (which includes any follow-up lines that start with a ;) must carry a comment telling what is tested.
Keep in mind that later this is the only way to tell one test case from the others without reading the code, so be as clear as you can be, but also be as brief as possible.
You are restricted to a single line, and you should keep it short enough to be displayed with a reasonable setting of ⎕PW.
Each test function must accept two Booleans as right argument, debugFlag and batchFlag
A> ### debugFlag and batchFlag
A>
A> debugFlag
A>
A> : A Boolean that tells the control functions (PassesIf, FailsIf, GoToTidyUp, see Flow control for details) what to do in case a test does not meet expectations: invoke a stop on the calling line for investigation or carry on and report the test as having failed.
A>
A> batchFlag
A>
A> : A Boolean that indicates whether the tests are executed as part of a batch job, meaning that no human can be asked a question or make a decisions, and reporting makes no sense.
A>
A> : A test function can use that to not execute its own tests in case it needs to interact with a human, returning the constant _NoBatchTest
A>
A> : When this flag is true and a test fails, the line number of the test that failed is reported at the end of the message.
There are some typical scenarios:
Run.RunThese.RunBatchTests.RunGUI. The GUI allows you pass a parameter namespace.All these functions call the function Run__ under the hood, which means that this is a generalized all-singing all-dancing function. If none of the above functions fulfils your needs, consider calling Run__ yourself, and use one of the other Run* functions as a template.
trapFlag)Traps all errors except that it does not influence the workings of the debugFlag.
debugFlag)This flag controls the behaviour of the control functions: PassesIf, FailsIf and GoToTidyUp — see Flow control for details.
If debugFlag flag is 0, any failing test within any test function just makes the test function quit, by default returning a symbolic name _Failure. See Symbolic names for details.
If debugFlag is 1 (defaults to he symbolic name _OK), then any failing check crashes right on the spot. This allows one to investigate what went wrong, and why.
stop)This makes the test framework stop just before the next test function is about to be executed. This allows you to trace test cases from top to bottom.
(You can achieve more than that, discussed soon)
These are tests that do not need a human in front of the monitor. Ideally, all tests should be “batchable” of course. However, in real life, this is not always possible for technical reasons, or the effort would be too high.
Note that all test cases get the batchFlag provided as part of the right argument, so they know what’s required, and can act accordingly.
Every test function must accept a right argument which is a two-item vector of Booleans:
debugFlag; 1 means that the test function runs in debug mode.
Assuming that all tests use the flow control functions provided by Tester2, the difference is that with debugFlag being 0 the test function would quit and (by default) return the symbolic name Failure, while with debugFlag being 1 the test function would crash on the spot, allowing the user to investigate.
batchFlag: 1 means that there is no user available in front of the monitor.
That allows a test function that requires a human for, say, some confirmation or some action to not carry out the test but return the symbolic name _NoBatchTest.
Every test function must return a result. You are advised to assign one of the symbolic names defined as read-only fields every instance of Tester2 comes with. This is much more readable than a simple integer, and it is easier to find as well. Also, you can add custom names. See Symbolic names for details.
Note that you will find that test cases need to be kept simple, and should not depend on each other (see Best Practices). That leads easily to a significant number of test cases in case you throw complex scenarios at them, like, say, MarkAPL.
It is pretty easy to get lost in a large number of test cases, less so when writing the first set but more so when later you need to delete obsolete test cases, add new test cases for new features or fixed bugs, or make changes to test cases that need, well, changing.
That’s why groups are quite important: they allow you to order test cases in a meaningful way.
It is also possible to execute all test cases belonging to a particular group (or some groups) with a single command.
You may also specify more than one group by…
use * as a wildcard character for matching one or more groups
The wildcard character can only be used at the end of a name.
~ character (“without”), , ~ and * (since version 3.7)Note that you can have group-specific initialisation and cleaning up.
Group selection requires to call RunThese:
RunThese 'Grp1_01' ⍝ Specific test case
RunThese 'Grp1' ⍝ Specific group
RunThese 'Grp*' ⍝ All groups starting with "Grp"
RunThese 'Grp1,Grp2' ⍝ Two specific groups
RunThese 'Grp*,Misc_1' ⍝ All groups starting with "Grp" & one specific test case
RunThese 'G*,~GP' ⍝ Everything that starts with "G" except "GP"
Although there are quite a number of symbolic names available to give feedback for many foreseeable problems (see Symbolic names for details), there will always be circumstances that cannot be foreseen. Therefore Tester2 allows you to define up to 9 custom symbolic names.
They are named from custom_1 to custom_9. They are initialised with ''. In order to use one you must assign a simple character vector. This character vector is shown in the “Result” column of the GUI etc.
An example would be (assuming that T is an instance of the Tester2 class):
T.custom_1←'Invalid version of Dyalog APL'
Imagine that a few of a large set of test cases leave behind files in the temp folder, and you have no idea which one it is. That is easy to find out when you check right after a test case was executed.
That’s why exec_before_each_test and exec_after_each_test were added as properties with version 1.1 to Tester2: they allow you to check whatever you want either just before or right after a test case was executed.
Another application is when a test case causes a sys error (aplcore) but it is not that particular test case that is causing the real problem but an earlier one. In that case you can execute 2 ⎕NQ'.' 'wscheck' after each test case, forcing the interpreter to perform a workspace integrity check; that will bring you much closer to the real culprit.
I found many other applications for this over the years.
When specified they must be the fully qualified name of a monadic function that may or may not return a result.
The right argument will be a two-element vector:
The name of the test function that is about to be executed (in case of exec_before_each_test) or was just executed (in case of exec_after_each_test).
A namespace with all the parameters.
Regarding the result there are two options:
In case no result is returned you need to either print a message to the session or make sure the function stops in case you find something not to your liking; you might need to keep ⎕TRAP local and set it accordingly in the function for this.
In case the function returns a result it must be a text vector. If that text vector is empty then Tester2 does not take any action. If it is a simple non-empty string this string will be printed to the session. Then Tester2 carries on.
No matter which of the Run* functions you are going to call, the workflow is always the same. (There is a minor difference when Run__ is used; this will be discussed later)
First you need an instance. Let’s assume that you have a project in #.Foo and that the project’s test cases are hosted by the namespace #.Foo.TestCases. You create an instance with:
T←⎕NEW #.Tester2 #.Foo.TestCases
From then on, all Run* functions, all symbolic names and all other helpers are available via the instance T. Some examples:
T.RunGUI ⍬
T._OK
T.PassesIf
T.⎕nl -3 ⍝ Produces a list of all public instance methods
No matter which of the Run* functions you will call, they all carry out the same steps:
Note that there is an instance property IniFolder. By default this points to CiderConfig.HOME if a namespace CiderConfig can be found in the namespace that hosts the test cases or its parent.
If there is no such namespace the default falls back to the current directory.
If your INI file(s) do not live in either place then you must set the IniFolder property in order to allow Tester2 to find the INI file(s).
First the Run* methods check whether there is a file testcases.ini. If this is the case the INI file is processed. Use this INI file to specify general stuff that does not depend on a certain computer/environment.
Then the Run* methods checks for a file testcases-{computername}.ini. If this file exists it is processed. Use this to specify stuff that does depend on a certain computer or environment.
Note that if one or both of the two INI files exist there will be a flat namespace T.INI, meaning that any sections defined in the INI file(s) will be ignored.
An example: if your test functions are hosted by a namespace Foo.TestCases and your INI file specifies a variable hello as holding “world” and you have created an instance with the name T inside #.Foo.TestCases then:
'world'≡#.Foo.TestCases.T.INI.hello
1
In the next step, the Run* method checks whether there is a function Initial in the hosting namespace. If that is the case, this function is executed.
Note that the function must be either niladic or monadic, and it may return no result at all or a Boolean result or a text vector.
Of course, you can simply execute → on a single line in your Initial function if any requirement is not met, but that would also mean that if you run your test cases as part of, say, an automated build process, then this would disrupt the workflow. (There are other reasons why this is not a great idea)
In such cases Initial should return a 0 indicating failure. Also, part of the initialisation might have been carried out, and a function Cleanup (discussed in a second) might get rid of any leftovers.
If the function is monadic, then a reference pointing to the parameter space is passed as the right argument. By checking this parameter space Initial can, for example, find out whether the batchFlag is set or not.
You may also put a variable inside that namespace which can later be referenced by the Cleanup function. However, in order to avoid name clashes you are advised to start the names of such variables with a ⍙ or a ∆ character.
Use Initial to create stuff that’s needed by all test cases, or tell the user something important (only if the batch flag is false of course).
Notes:
You can have separate Initial functions for specific groups. Use this to initialise stuff that is only needed for a certain group, like a database connection etc.
For a group “MyGroup”, such a function must be named Initial_MyGroup.
After executing all test cases Tester2 will look for a function Cleanup in the hosting namespace. If there is such a function it will be executed.
Of course you can also have group-specific Cleanup functions. For a group “MyGroup”, such a function must be named Cleanup_MyGroup.
You can have initialisation functions for groups. It’s recognized by naming convention: for a group foo the function’s name must be Initial_foo.
The rules are the same as for the global Initial function, although the consequences are different:
When Initial_foo returns a Boolean and that is a zero, then no test function belonging to the group foo will be executed, but Tester2 will carry on executing other test cases.
Accordingly, when Initial_foo returns a non-empty text vector, then no test function belonging to the group foo will be executed, but Tester2 will carry on executing other test cases. The result will be added to the log.
A group-specific initialisation function is executed right before the first test case of that group is executed.
Now the test cases are executed one by one, or, if groups are defined, one group after the other.
After the last test case was executed, the Run* function checks whether there is a function Cleanup in the namespace hosting your test cases. If that’s the case, then this function is executed.
Any Starting with version 4.2, a Cleanup function should either return a shy result (which will be ignored) or no result at all.Cleanup function may or may not return a Boolean result with 1 indicating success. This has an effect only on the log.
It might accept a right argument, but this is optional: it might as well be niladic. If it does accept a right argument it will get the parameter namespace passed.
Notes
Cleanup is be executed no matter whether there was or was not a function Initial, and if there was, no matter whether it returned a 0 or a 1 or nothing at all.You can have clean-up functions for groups. It’s recognized by naming convention: for a group foo the function’s name must be Cleanup_foo.
The rules are the same as for the global Cleanup function.
Naturally a group-specific Cleanup function is called after the last test function of that group got executed.
Finally, the namespace INI holding variables populated from your INI file(s) is removed from the instance.
There might be situations when you’ve executed some but not all test cases, and now you want to exit the test framework, typically while you are in a test function. Now the obvious choice is )Reset, or execute just →.
However, there are situations when you need things to be cleaned up, like closing files, deleting folder(s), shutting down a server, stuff like that.
In such a case the framework should clean up (execute any Cleanup function) etc.
This can be achieved by calling the instance method QuitTests. This function signals a QuitEvent which is trapped and processed in a specific way by the test framework.
You are advised to make using this mechanism a habit when you want to exist a test suite early.
Tester2 allows you to stop at strategically important points in time.
You might want to trace through a test function from top to bottom without tracing through the test framework. That can be achieved by passing a 1 as left argument to any of the Run* functions. The test framework will stop just before the test function(s) get executed.
Initial function(s)You can force Tester2 to stop just before any Initial function gets executed. That can be achieved by passing a 2 as left argument to any of the Run* functions.
Cleanup function(s)You can force Tester2 to stop just before any Cleanup function gets executed. That can be achieved by passing a 4 as left argument to any of the Run* functions except RunGUI and Run__.
You may combine stops; for example, to make Tester2 stop on every Initial, every test and every Cleanup function just specify the total as left argument: 1+2+4 = 7
Any other combination (3, 5, 6) is valid as well.
In case the tests are called by a user (rather than a batch process), you can also specify a “?” as stop. That makes the test suite offer all options in a list. You can then simply select the options you are interested in:
--- Stop just before... -----------------------
1. Tests
2. Initial
3. Cleanup
4. Tests & Initial
5. Tests & Cleanup
6. Initial & Cleanup
7. Test, Initial & Cleanup
8. Nowhere
9. Cancel
Select one item (q=quit) :
Note that the GUI provides a combo box with all possible values:

Usually one would like to know how much of the code of an application is covered by test cases. Ideally that should be 100%, but that is rarely achievable.
In order to improve on this, one needs to know how much code is covered, and also which parts of the code are not covered.
Since version 2.3 Tester2 can cooperate with the Tatin package CodeCoverage which is capable of collecting the necessary data and compiling a report.
If you want Tester2 to cooperate with CodeCoverage, assign a reference pointing to an instance of CodeCoverage to the Tester2 property codeCoverage.
Tester2The methods fall into four groups:
Run, RunBatchTests, RunGUI, RunThese and Run__ are running all or selected test cases.
FailsIf, PassesIf and GoToTidyUp control the program flow in test functions. The test template (see GetTestTemplate for details) contains examples of how to use these functions.
These functions return a result (Boolean) in case debugFlag is a 0 but make the calling Test_* function crash otherwise, allowing you to investigate a failing test case right on the spot. Note that the setting of the trapFlag has no bearing on this.
This is achieved by the functions FailsIf, PassesIf and GoToTidyUp signalling an error 999 that can be trapped with, say:
⎕TRAP←(999 'C' '∘∘∘ ⍝ Deliberate error')(0 'N')
That’s why the template for a test function carries such a statement and keeps ⎕TRAP local.
Note that GoToTidyUp allows you to jump to a label ∆TidyUp with a statement like this:
→GoToTidyUp ~expected≡result
This is useful in case a test case needs to do some cleaning up like deleting a temporary file created by the test case, so just jumping out is not an option. In case the right argument is 1 (rather than 0) it causes a crash in debug mode and carries out the jump otherwise.
You can establish a test function template by calling the instance method GetTestTemplate.
You must provide a number as right argument. The test function will be named Test_{yourNumber}. The number must be between 1 and 999 and will be formatted to a three-character vector.
You can also provide a group name via the optional left argument.
Check the examples which assume that an instance of Tester2 is available as T, and that you are inside the (ordinary, non-scripted) namespace that hosts the test cases:
≢'T'⎕NL 3
0
T.GetTestTemplate 3
Test_003
≢'T'⎕NL 3
1
'Misc' T.GetTestTemplate 1
Test_Misc_001
≢'T'⎕NL 3
2
Notes:
In case such a test function already exists it will be overwritten, but first you will be prompted for confirmation.
For technical reasons such a template function cannot be established by GetTestTemplate when the namespace hosting the test cases is scripted. If that is the case you will be prompted for copying the code to the clipboard; it is then up to you to insert the code into the script.
These are the public read-only instance fields that act like constants:
| Name | Meaning |
|---|---|
_OK |
Passed |
_Failed |
Unexpected result |
_NoBatchTest |
Not executed because batchFlag was 1. |
_NotApplicable |
This test is not applicable here and now |
_NotImplemented |
Attempts to test a feature that has yet not been implemented |
_InActive |
Not executed: the test case is inactive (not ready, buggy, …) |
_IncompatibleVersion |
Regarding the version of Dyalog currently running |
_LinuxOnly |
Not executed because runs under Linux only |
_LinuxOrMacOnly |
Not executed because runs under Linux/Mac OS only |
_LinuxOrWindowsOnly |
Not executed because runs under Linux/Windows only |
_MacOrWindowsOnly |
Not executed because runs under Mac OS/Windows only |
_MacOnly |
Not executed because runs under Mac OS only |
_NoAcreTests |
Not executed because it’s acre-related but acre is not around |
_NoCiderTest |
Not executed because it’s Cider-related but Cider is not around |
_SkippedByUser |
Returned in case the user refused to execute a test case |
_WindowsOnly |
Not executed because runs under Windows only |
Use one of these to assign an explicit result within any test function. The advantages of this approach:
_InActive, while searching for its number might well be pointlessNotes:
ListSymbolicNames availableBy putting a comment on the line where a constant is assigned you can make sure that this comment is, together with the name of the constant, put into the GUI’s “Result” comment
For example, assuming that Tester2 was instantiated with T←⎕NEW Tester2, then this:
R←T._Inactive ⍝ Waiting for fix for bug 1291
would pop up as “Inactive: Waiting for fix for bug 1291” in the “Result” column for the associated test function.
These are functions that are not required in order to run test cases, but can make a programmer’s life significantly easier.
Two scenarios are common:
Run all test cases and create data on the fly that allow creating a code coverage report.
Such tests may well interact with the user.
Run all tests that do not require a user (batch tests), typically as part of some automated process
Most importantly this returns a Boolean that either indicates 100% success with a 1, or that at least one test case failed, indicated by a 0.
Tester2 can establish helpers in the namespace that host your test cases covering these two scenarios.
Assuming that you are currently in that namespace, and that it contains Tester2 (and also CodeCoverage if you are after a code coverage report) then this is all that is required:
Tester2.EstablishHelpers ⎕THIS
"Prepare", "RunTests" & "RunBatchTests" successfully established in ...
Note that you should read and amend the helpers after having established them, in particular Prepare.
First make sure that the helpers are established in the namespace that hosts your tests. If you are inside that namespace and have instantiated Tester2 as T:
T.EstablishHelpers ⎕THIS
"Prepare", "RunTests" & "RunBatchTests" successfully established in ...
A niladic function that does not return a result. It asks whether the user wants to get a code coverage report.
If there is already such a report the user might want to append data, for example when you need to run your test suite under Windows, Linux and Mac-OS.
Such tests might well rely on the user to answer questions. The debug flag is set, so when a test case has a problem, it stops on the spot and allows the user to investigate the problem.
(success log)←RunTestsInBatchMode
This does not do code coverage. The debug flag is off, so in case a test case fails it does not stop.
This is typically run as part of an automated process.
It returns a two-item vector as result:
A Boolean indicating success with a 1 and failure with a 0. It’s a failure when at least one test case failed.
A vector of text vectors with a detailed report
Prepare is called by RunTests, but it can also be useful if you want to prepare for a run but have special needs that are not covered by RunTests, for example when you want to run only a specific group of test cases.
You only need to read this if the top-level helpers do not cover your needs.
This is about Tester2’s Run* functions. These should be flexible enough to cover even exotic needs.
Run{(rc log)}←{stop} Run debugFlag
Run requires a Boolean right argument. A 1 makes the test framework stop on a line that fails to return the expected result (PassesIf, FailsIf, GotoTidyUp) while a 0 does not.
The optional left argument defines whether the test framework should stop right before any of the test cases is executed (1) or not (0, which is the default), or other functions, see “Make the test framework stop” for details
RunGUIWith RunGUI you can achieve the same as with Run but it is a Windows-only feature. It might be easier to start with RunGUI, but you might switch to Run later, if only because it is significantly faster.
However, there are situations when RunGUI is indispensable: Tester2’s own test cases are almost impossible to follow without it, for example. It’s also useful to demonstrate the features of the Tester2 class.
RunGUI requires only a right argument; if this is empty all test cases will be executed. You can specify numbers or a group name or mix a group name with one or more numbers. You can also precede a group name with a ~ (without) to execute all test cases but the members of that group.
Notes:
CloseGUI.CreateParms, make amendments and pass the parameter space as the optional left argument to RunGUI. However, since you can make those amendments in the GUI itself this is useful only to specify defaults for the GUI.RunTheseThis method is particularly helpful while developing/enhancing stuff: the function allows you to run just selected test functions rather than a whole test suite.
If you now think, well, why not just call any function Test_001 myself then imagine a situation when all your test cases depend on an INI file or the execution of Initial or both. That is exactly the advantage of RunThese: it carries out all these steps for you, and also executes the Cleanup function in case there is one.
Also, the flow control function rely on the existence of certain variables which in turn require an instance of the Tester2 class anyway.
RunThese offers the following options:
T.RunThese 1 ⍝ Execute test cases 1 that do not belong to any group
T.RunThese 'Group1' ⍝ Execute all test cases belonging to Group1
T.RunThese '~Group1' ⍝ Execute all tests but those belonging to Group1 (without)
T.RunThese 'Group1' (2 3) ⍝ Execute test cases 2 & 3 of Group1
T.RunThese 'Group1' 2 3 ⍝ Same as before
T.RunThese 'Group1,Group2' ⍝ Execute two groups
T.RunThese 'Misc' ⍝ Execute all test cases of the group "Misc"
T.RunThese 'L*' ⍝ Execute all test cases of all groups starting with "L"
T.RunThese 'Group*,~Group_Foo' ⍝ Execute all groups named "Group*" but "Group_Foo"
T.RunThese 'Group1_001' ⍝ Executes just this test case
T.RunThese 'Test_Group1_001' ⍝ Same as before
Sometimes you want to trace through test cases. This can be achieved by specifying a 1 as the (optional) left argument. RunThese would then stop just before any test case is executed, after processing any INI file(s) and executing any Initial function.
RunBatchTests{(rc log)}←{stop} RunBatchTests debugFlag
This is the same as Run except that it passes a 1 as batchFlag to the test functions. This allows the test function itself to quit because it’s not batchable. This is usually because a test requires a human in front of the monitor, and if such a human is not available right now, then there is no point running such tests.
Run__{(rc log)}←Run__ ps
This is a generalized function that is effectively called by all the other Run* functions. In case the other functions don’t suit your needs, you can use it yourself, although this requires a little more effort:
ps←T.CreateParms ⍬
ps._∆List ⍝ List the defaults
batchFlag 0
debugFlag 0
guiFlag 0
stop 0
testCaseNos
trapFlag 1
⍝ Make amendments:
ps.stop←1
ps.testCaseNos←'Misc' (1 2 3)
⍝ Call the Run__ function:
(rc log)←T.Run__ ps
There are a couple of methods available that assist you in managing test cases.
The examples stem from the Fire project (https://github.com/aplteam/Fire).
We assume that you have instantiated Tester2 as T.
ListGroups lists all groups:
T.ListGroups
acre
Cider
InternalMethods
List
Misc
Replace
ReportGhosts
Search
ListSymbolicNames lists all symbolic names.
ListTestFunctions requires a right argument (empty or group name) and accepts an optional left argument (test case numbers). The group name might carry a trailing * character as a wildcard.
≢T.ListTestFunctions'Foo'
0
≢T.ListTestFunctions'foo'
3
T.ListTestFunctions'foo'
Test_foo_01 Pro-forma test used to test `RunThese 'foo' 1`
Test_foo_02 Pro-forma test used to test `RunThese` with `1 2` as right argument
Test_foo_03 This is used to check whether T._Inactive is processed as intended AND ...
≢T.ListTestFunctions'Mi*'
33
1 7 99 ListTestFunctions 'Mi*'
Test_Misc_01 Pro-forma test used to test `RunThese` with a 1 as right argument
Test_Misc_07 Exercise the `RunThese` method
≢ T.ListTestFunctions''
87
With a large number of test cases you might prefer to get them into an edit window rather than printing them to the session. This can be achieved by passing “view” as left argument:
'view' T.ListTestFunctions ''
Notes:
The result is a matrix with two columns: names in the first column and the first line after the header in the second column
If the result is too wide to fit into ⎕PW measures are taken
Occasionally you might want to edit some or even all test case functions. That can be easily achieved:
T.EditTestFunctions'' ⍝ Edit all test cases
T.EditTestFunctions T.ListTestFunctions'ZZZ' ⍝ Edit all of the group "ZZZ"
Renaming a test function is harder (and more dangerous) than you might think at first glance, hence the method RenameTestFnsTo is available to assist you.
The syntax:
'oldname' T.RenameTestFnsTo 'newname'
Let’s assume that at first you started numbering your test functions. Soon you come to realize that groups would be helpful. But it is awkward to have some test functions carrying just numbers and others group names and numbers. Therefore, it is a good idea to rename those with just a number into, say, Test_Misc_ followed by the number.
For example:
'Test_001' T.RenameTestFnsTo 'Test_Misc_001'
RenameTestFnsTo does a couple of things for you:
Test_001 to Test_Misc_001Test_Misc_001Test_Misc_001Test_001Test_001Note that RenameTestFnsTo can deal with groups as well. For example:
'Foo' T.RenameTestFnsTo 'Misc'
Assuming that there is a group “Foo”, then all members of that group would be renamed so that they become members of the group “Misc”.
If there is already a group “Misc” then numbering would start with the highest previous number plus 1.
When starting to implement test cases you are advised to leave gaps: Test_001, Test_010 etc. The same holds for numbering within groups.
Try to keep your test cases simple and test just one thing at a time, for example just one method.
For more complex methods (for example when the method accepts different kinds of arguments) create a group with the method name as group name.
Create everything you need on the fly and tidy up afterwards. Or more precisely, tidy up (leftovers!), prepare, test, tidy up again. In other words, make the test case “stand-alone”.
The exception from this rule is when all test cases require the same pre-condition like, say, a database connection. In that case establish what’s needed in a function Initial and use a function CleanUp to get rid of it.
If all test functions of a certain group share the same requirement use Initialisation for groups.
Avoid a test case relying on changes made by an earlier test case. This can be a tempting thing to do, but you are likely to regret it later.
Having said this, in rare cases it is required to build up on other test cases for technical reasons, or in order to avoid a significant performance penalty.
Notice that the DRY principle (don’t repeat yourself) can and should be ignored when it comes to test cases: any test case should read from top to bottom like an independent story that can be understood by itself.
It might be a good idea for all test functions to tidy up first, just in case this test case has failed earlier and left some debris behind.
It’s common practice to implement a test case for every bug, for bugs tend to make comebacks; such tests prevent that from happening.