Module exports for testing, is this possible?

I just refactored some tests out into a separate .plt file and it took me a while to get it all stable again but I realised that when the tests were in the same file it was cleaner because now I have had to export the predicates that I want to test.

Is there a way to say “export these for unit testing only” and then list them, maybe something like:

I know I can use Module:Predicate to directly call from the test, which solved my problem, but it means I have to do this all the time.

Have I missed a trick?

TIA
Sean

Typically, cross-module calls are considered ok for testing and debugging (and for some real code). You can dynamically import any predicate from a module. Don’t tell @pmoura :slight_smile:

:- export(m:p/1).
:- import(m:p/1).
1 Like

Superb!!!

Thanks Jan. In the last week since the lockdown, I have absolutely dived into SWI and it just keeps on giving.

:slight_smile:

I read the documentation and as usual it’s all there… problem is though I didn’t read that far in the first place. I started breaking my project into modules this week and when I got the “hang” of it, I ploughed on and looking at the title “An alternative import/export interface” I guess the word alternative was “don’t need this”!

I hereby do solemnly swear never to use import/1 other than for tests!

:smile::smile::smile: I agree with you. With tests in a separate module (as with plunit) or a separate object (as with lgtunit), we often need to test more than just the exported or public predicates. Ideally, just be calling the exported or public predicates, all predicates in the unit would be triggered. But in practice that may not provide the sought code coverage. So, (ab)using :/2 to call auxiliary predicates (in the case of Logtalk, there’s a <</2 context-switching debugging control construct that can be enabled and used also for testing) is fine in this particular case.

I don’t like this interface much, but it can be useful for dynamically created modules and compatibility with some other Prolog systems. And for dirty hacking …

1 Like

It’s a frequent issue for many languages I guess. For me though, I like to TDD my way through as I am still a relative plank / n00b with Prolog!

One of the frustrations is that when a predicate fails, it all fails and you are left scratching your head. I learned today I can put a call to gtrace and have the debugger pop open when truly stuck…I always view using the debugger as a sign of personal failure :smiley:

This week I also used properly “print_message” thanks to Anns great tutorial on the matter. It’s all coming together… I just don’t know what it will be when it’s finished…

You are correct. In some languages it perplexes many new programmers because they think the modifiers like private mean the code is inaccessible on the physical machine when in reality it is just a concept used by the compiler. So it is not a guess, it is a problem that many but not all programming languages have.

EDIT

There are many articles/blogs/post like this on the internet: Testing Private Methods with JUnit and SuiteRunner

Just search for unit test private method.

1 Like
Warning: /home/sean/Documents/code/prolog/the-f-word-project/tokeniser.plt:4:
	import/1: tokeniser:new_token/4 is not exported (still imported into plunit)

Is there a way to turn this off or am I stuck with warnings because I dared to use a dirty hack for testing? I might as well just make it all exported from the module for an easy life…

Don’t do that as many programmers know that predicates that are not exported should not be called outside of the module without good reason, e.g. testing. Another reason that the predicates may not be exposed is because they implement variations depending on conditions like which OS they are on or which version of Prolog they are emulating. Calling the non-exported predicates can have disastrous results when in other context, e.g. run on another OS, etc. Do yourself a favor and be very judicial about which predicates get exported and why and in the long run your code will be better.

However in some cases it is later realized that a predicate should be exported because it has been enhanced or the programmer felt it should not be exported but others point out reasons that it should be exported.

A case in point where a predicate is not exported and I used it only to find out that I was using the module incorrectly. The predicate that was not exported was has_type/2 and the predicate to use was either must_be/2 or is_of_type/2

1 Like

That is what the :- export(tokeniser:new_token/4). that you should add before the import does. You first tell another module to export one of its private predicates (that is the dirty part, but ok for this purpose), then you import (you can now also use :- use_module(...)).

1 Like

Dirty hack indeed, even if just for this purpose. Something along the lines of Logtalk’s <</2 or ECLiPSe’s @/2 control constructs would be arguably cleaner.

Well, good old Quintus Module:Goal works fine, but apparently @emacstheviking doesn’t like to type that all over the place. Note that another way is adding a clause like this:

 new_token(A,B,C,D) :- tokeniser:new_token(A,B,C,D).

In this case this does precisely what you (probably) want: call new_token/4 in the context of tokenizer. The @/2 is also in SWI-Prolog, but not as an operator. It was added for compatibility reasons and I’ve found a few use cases since.

1 Like

Sigh. I didn’t realise that I had to use export first./…I mean I actually saw your reply but I just thought you were showing me export and import…I had no notion they were to be used together like that! :smiley: Sometimes I wonder how I manage to dress myself.

At least you have found the path and are walking on it. :slightly_smiling_face:

:smiley: yes sir!

I have to say, having been in software development since the age of 19, after 35 years, I still love learning things, usually the hard way and ever since I discovered Lisp about 20 years ago, Prolog was always “that language” I had yet to confront. I think I was 7 years old when Prolog('72) was released so I can be excused for not being aware of it then!

On and off (more off than on to be honest) over the last years I have now started to realise that most languages are full of stuff nobody uses or needs. After a while you realise that most “software” is either number crunching or string bashing and pretty much that’s all you need.

I am continually blown away by the things I learn about Prolog. I have been lucky enough to obtain real books of Clocksin&Mellish, Learn Prolog Now!, Art of Prolog, Craft of Prolog, and Prolog Programming in Depth and between them I still know I have a lot to learn!

Might as well say a big big thanks to everybody around these here parts for such great answers and patience with me… “logic” programming is something that you could spend a lifetime understanding the nuances, only in the last couple of weeks have I truly grasped (I think!) what choice points, cuts and backtracking really are doing. Loving it!

4 Likes