2

As an example for this question, I have a very simple Prolog file main.pl, in which I've defined the colours of some shapes.

colour(circle, red).
colour(triangle, red).
colour(square, blue).

Now below that I define a predicate same_colour/2, which is true if both S1 and S2 are the same colour.

same_colour(S1, S2) :-
    colour(S1, C),
    colour(S2, C).

Testing at the top level indicates that this predicate works as expected.

?- same_colour(circle, triangle).
true.

?- same_colour(circle, square).
false.

I am trying to write unit tests using SWI-Prologs unit testing framework plunit for same_colour/2, but I want to declare facts within each individual test that are only true within the scope of that test. I've tried using the setup option for individual tests, as well as asserta, neither of which work. All of the below tests fail.

:- begin_tests(same_colour).

test(same_colour) :-
    colour(shape_a, colour_1),
    colour(shape_b, colour_1),
    same_colour(shape_a, shape_b).

test(same_colour) :-
    asserta(colour(shape_a, colour_1)),
    asserta(colour(shape_b, colour_1)),
    same_colour(shape_a, shape_b).

test(same_colour, [
    setup(colour(shape_a, colour_1)),
    setup(colour(shape_b, colour_1))
]) :-
    same_colour(shape_a, shape_b).

:- end_tests(same_colour).

I've also tried:

test(same_colour, [
    setup(asserta(colour(shape_a, colour_1))),
    setup(asserta(colour(shape_b, colour_1))),
    cleanup(retract(colour(shape_a, colour_1))),
    cleanup(retract(colour(shape_b, colour_1)))
]) :-
    same_colour(shape_a, shape_b).

that is, first declare that colour(shape_a, colour_1) and colour(shape_b, colour_1) are facts, do the test, then 'undeclare' them. However, this test fails as well. Using trace it seems that colour(shape_a, colour_1) is never asserted (or at least is not true while my test is running.)

Call: (18) plunit_same_colour:'unit body'('same_colour@line 13', vars) ? creep
Call: (19) same_colour(shape_a, shape_b) ? creep
Call: (20) colour(shape_a, _G738) ? creep
Fail: (20) colour(shape_a, _G738) ? creep
Fail: (19) same_colour(shape_a, shape_b) ? creep
Fail: (18) plunit_same_colour:'unit body'('same_colour@line 13', vars) ? creep

I can understand now why the first two tests do not work. In the first I am testing whether colour(shape_a, colour_1) is true, when it hasn't been declared before, and in the second I just don't think it is correct to use asserta from within a predicate definition. Though it feels like something similar to my third or fourth test should be able to achieve what I am trying to do?

false
  • 10,264
  • 13
  • 101
  • 209
James Hiew
  • 6,040
  • 5
  • 27
  • 41

2 Answers2

2

testing your tests :)

:- module(colour_test, [same_colour/2]).
:- use_module(library(plunit)).

colour(circle, red).
colour(triangle, red).
colour(square, blue).

same_colour(S1, S2) :-
    colour(S1, C),
    colour(S2, C).

:- begin_tests(same_colour).

test(same_colour) :-
    colour(shape_a, colour_1),
    colour(shape_b, colour_1),
    same_colour(shape_a, shape_b).

test(same_colour) :-
    asserta(colour(shape_a, colour_1)),
    asserta(colour(shape_b, colour_1)),
    same_colour(shape_a, shape_b).

test(same_colour, [
    setup(colour(shape_a, colour_1)),
    setup(colour(shape_b, colour_1))
]) :-
    same_colour(shape_a, shape_b).

:- end_tests(same_colour).

yields

1 ?- run_tests(same_colour).
% PL-Unit: same_colour 
ERROR: /home/carlo/prolog/so/colour_test.pl:14:
    test same_colour: failed

ERROR: /home/carlo/prolog/so/colour_test.pl:19:
    test same_colour: received error: asserta/1: No permission to modify static procedure `colour_test:colour/2'
Defined at /home/carlo/prolog/so/colour_test.pl:4
ERROR: goal unexpectedly failed: colour(shape_a,colour_1)
 done
% 2 tests failed
% 0 tests passed
false.

that is, if you want to modify the DB at runtime, let Prolog know, adding:

:- dynamic colour/2.

now it's going better:

2 ?- run_tests(same_colour).
% PL-Unit: same_colour 
ERROR: /home/carlo/prolog/so/colour_test.pl:16:
    test same_colour: failed

.. done
% 1 test failed
% 2 tests passed
false.

but the DB is 'dirty':

3 ?- listing(colour).
:- dynamic colour_test:colour/2.

colour_test:colour(shape_b, colour_1).
colour_test:colour(shape_a, colour_1).
colour_test:colour(circle, red).
colour_test:colour(triangle, red).
colour_test:colour(square, blue).

true.

Of course, it's long time known that modifying a program at runtime it's a dangerous activity... anyway, setup/cleanup are goals, so maybe you want

test(same_colour, [
    setup(( assertz(colour(shape_a, colour_1)),
            assertz(colour(shape_b, colour_1))
    )),
    cleanup(retractall(colour(_, colour_1)))
]) :-
    same_colour(shape_a, shape_b).
CapelliC
  • 59,646
  • 5
  • 47
  • 90
1

You have a straightforward solution using in alternative Logtalk's unit test framework:

https://github.com/LogtalkDotOrg/logtalk3/blob/master/tools/lgtunit/NOTES.md

SWi-Prolog is one of the twelve supported Prolog systems. No need for hacks with dynamic/1 directives or for cleanup goals.

Assume, for example, that you have three sets of facts that you want to test your code with. Furthermore, assume that the code to be tested is defined in the same_color.pl Prolog file. Simply define a unit test object per each set of facts:

:- object(colors1, extends(lgtunit)).

    :- include('same_color.pl').

    colour(circle, red).
    colour(triangle, red).
    colour(square, blue).

    test(colors1) :-
        ...

:- end_object.

Define objects, e.g. colors2 and colors3 for the other sets of facts. Then:

| ?- logtalk_load(lgtunit(loader)).
...
| ?- logtalk_load([colors1, colors2, colors3], [hook(lgtunit)]).
...
| ?- colors1::run, colors2::run, colors3::run.

Or simply automate the unit tests by defining a tester.lgt help file and running the logtalk_tester automation shell script. Plenty of examples, including a sample tester-sample.lgt file in the Logtalk distribution. If you find it odd to use Logtalk unit testing tool to test Prolog code, know that Logtalk distribution includes a full Prolog standards conformance test suite, which provides you with further examples.

Paulo Moura
  • 18,373
  • 3
  • 23
  • 33