Writing unit tests in Logtalk

Logtalk provides simple but portable support for writing unit tests. This support is provided by a library object, lgtunit, inspired on the works of Joachim Schimpf (ECLiPSe library test_util) and Jan Wielemaker (SWI-Prolog plunit package). You can load it using the goal:

| ?- logtalk_load(library(lgtunit)).

The interface of the lgtunit object is quite simple and can be found e.g here:

https://logtalk.org/library/lgtunit_0.html

Most of the examples found on the current Logtalk distribution include a set of unit tests, together with a handy loader file named tester.lgt. Typically, this loader file loads the code you want to test, the unit test framework, the unit tests themselves, and send a run/0 message to the unit test objects. Check, for instance, the example:

https://github.com/LogtalkDotOrg/logtalk3/tree/master/examples/people

In order for you to write your own unit tests, start by defining objects extending the lgtunit object. These objects must be compiled using the option hook(lgtunit). For example:

| ?- logtalk_load(my_tests, [hook(lgtunit)]).

After successful compilation and loading of your unit test objects, you can run them by typing:

| ?- my_tests::run.

Logtalk unit tests can be written using any of three different dialects. The most simple dialect is:

test(Test) :- Goal.

This dialect allows the specification of tests that are expected to succeed. Tests that are expected to fail can be written by using the \+/1 built-in predicate. However, tests that are expected to throw an error cannot be specified. A second dialect overcomes this restriction:

succeeds(Test) :- Goal.
fails(Test) :- Goal.
throws(Test, Error) :- Goal.

This is a straightforward dialect. For succeeds/1 tests, Goal is expected to succeed. For fails/1 tests, Goal is expected to fail. For throws/2 tests, Goal is expected to throw Error. A third supported dialect is:

test(Test, Outcome) :- Goal.

In this case, Outcome can be the atom true, the atom fail, or the exception term that the test is expected to throw. In all cases, Test is an atom, uniquely identifying a test. Simply use the dialect that is the best fit for your application.

That’s all. Happy testing!

Note: when using the <</2 control construct to access and test an object internal predicates, make sure that the objects are compiled with the context_switching_calls flag set to allow.