1

I want to write unit tests in SWI-Prolog (version 7.6.4) in order to streamline and automate testing, which is currently done only in a manual, ad-hoc fashion.

The files to be tested contain complex algorithms that make use of predicates from modules, which in turn operate on user-defined predicates (that serve as input data or problem instance). As a minimal example, consider the following:

File 'graph.pl' (input data and algorithm):

:- use_module(path).

edge(a,b).
edge(b,c).
edge(c,d).

reachable(X,Y) :-
    path(X,Y), !.
reachable(X,Y) :-
    path(Y,X), !.

File 'path.pl' (the module):

:- module(path, [path/2]).

path(X,X).
path(X,Y) :-
    user:edge(X,Z),
    path(Z,Y).

Queries run as expected:

?- [graph].
true.

?- reachable(a,a).
true.

?- reachable(a,d).
true.

?- reachable(d,a).
true.

Let us include these queries into a test file 'graph.plt':

:- begin_tests(graph).
:- include(graph).

test(1) :-
    reachable(a,a).
test(2) :-
    reachable(a,d).
test(3) :-
    reachable(d,a).

:- end_tests(graph).

When I then run the tests, I get:

?- ['graph.plt'].
true.

?- run_tests.
% PL-Unit: graph .
ERROR: /home/jens/temp/graph.plt:6:
    test 2: received error: path:path/2: Undefined procedure: edge/2
ERROR: /home/jens/temp/graph.plt:8:
    test 3: received error: path:path/2: Undefined procedure: edge/2
done
% 2 tests failed
% 1 tests passed
false.

That is to say, when called from within the test suite, the module is no longer able to 'see' the predicate 'edge' under the 'user:' namespace. Is this a bug, or am I missing something?

Jens Classen
  • 176
  • 10
  • Implicit in the `path` module is that only a **single** dataset will be used at any given time as the dataset is hard-coded to be in `user`. I.e. you load a single dataset and restart to try a different dataset. Intended? – Paulo Moura Jul 30 '19 at 08:57
  • Yes, at least that is how it is currently done, but I'm always open to suggestions for better solutions (best practices) in this regard. Perhaps by means of dynamic importing? I guess this would help to make requirements/dependencies explicit ("which predicates does the `path` module expect?"). Anyhow, I would expect that a call from within the test box behaves precisely like a call from `user`... – Jens Classen Jul 30 '19 at 18:11
  • Using Prolog modules, one possible solution is to pass the module that holds the data to be tested as an additional argument to the predicates. An alternative is to use Logtalk parametric objects and pass the object holding the data using a parameter. In this later case, the predicates don't require an additional argument. Providing a default, e.g. `user` is easy by extending the parametric objects with objects that bound the parameter to `user`. In both solutions, loading and testing multiple datasets can then be easily done without restarting. – Paulo Moura Aug 01 '19 at 10:09
  • Thanks for the suggestions, I will take a look. In any case, it will require some major restructuring/refactoring of the code, but I think at this point this is unavoidable. – Jens Classen Aug 01 '19 at 20:58
  • If you ask a question in this particular topic, I can expand my comment into a detailed answer. – Paulo Moura Aug 01 '19 at 22:23
  • I posed [a new question](https://stackoverflow.com/questions/57341339) on this topic. – Jens Classen Aug 03 '19 at 19:04

1 Answers1

2

I found the answer myself. It turned out that nothing was wrong here, but this problem was just another case of RTFM. From the PlUnit documentation:

3 Using separate test files

Test-units can be embedded in normal Prolog source-files. Alternatively, tests for a source-file can be placed in another file alongside the file to be tested. Test files use the extension .plt. The predicate load_test_files/1 can load all files that are related to source-files loaded into the current project.

So if using separate .plt files for testing, you are supposed to load the original source file first, then call load_test_files/1 (possibly with make or make(all) as options), and then run_tests:

?- [graph].
true.

?- load_test_files([]).
true.

?- run_tests.
% PL-Unit: graph ... done
% All 3 tests passed
true.
Jens Classen
  • 176
  • 10