I'm testing my module's procedures in an external test module. This works great for public procedures but not at all for private ones since they are not exported. I have been exporting these just so I can write test code. This has the drawback that the public interface of the module is bigger than it needs to be.
An alternate scheme is to use the isMainModule variable and test the code inside this block inside the main module. This works well for the private procedures, but not as well for the public ones, since this environment is a lot different than how the user accesses the procedures.
Another alternate scheme is to use both approaches, extern modules for the public procedures and inside the main module for the private procedures. This has the disadvantage that the module's tests are in two places and both need to be run.
If there was a good way to conditional make a procedure public, for example public in normal builds and private when release is defined, that would allow you to put all the test code for a module in its test module. The test code would have to be inside a "when not defined(release):" block. This sounds like a good approach to me.
Is there already a good solution to this issue? If not, is it possible to come up with a good way to do this using templates or macros?, that is more elegant than:
when defined(release):
proc myPrivate(value: uint8): string =
...
else:
proc myPrivate *(value: uint8): string =
...
Something like:
dpub = ???
proc myPrivate dpub(value: uint8): string =
...
A bit meta to this topic but isn't it better to test only the public procedures?
Otherwise if you test your private procs, they will hinder you while refactoring (limiting internal design) instead of helping you.
unit-testing is, per se, a little bit overrated. A good explanation about that can be found here: https://rbcs-us.com/documents/Segue.pdf
Better to do some integration-tests; thats also an excellent starting point for others to get the idea of your software. And then a huge upside comes into play if you intend to do refactoring..
Using include is an interesting idea, it adds another dimension to consider. Including allows you to group all module tests together which I want, however it requires you run in the main module. I want to run in an external module to more closely match how a user would use the code.
I didn't anticipate having to defend unit testing. I've detected a testing backlash here and other places. The pendulum swings back a little. It's true you can go overboard with testing, or anything, but I think there is a lot of value to writing tests. It gives you confidence to refactor. If the private tests no longer apply, it is easy to delete them. It's harder to test private procedures thoroughly by testing the public procedures alone.
The reason for my post is for help and guidance on using the meta programming features of nim. I find this part of nim exciting. But after reading the tutorial, manual, the macro module and any thing I could find, I still need some help getting started.
How should I use the nim language macro capabilities to conditionally make procedures public?
This particular case is easy to solve in C by using a #define. I don't see an easy solution in nim yet.
Maybe I could write compile time code at the bottom of the module to loop through all the procedures and add the ident "*" to ones I want? How do you get access to the AST for changing? I didn’t see a getRoot kind of proc in the macro module.
I’m thinking about supporting a new block type, where all procs in it conditionally become public. How does this sound? Is there sample code I should look at?
import macros
macro tpub*(x: untyped): untyped =
## Marks a proc with an export asterisk when ``-d:testing`` is defined.
expectKind(x, RoutineNodes)
when defined(testing):
let n = name(x)
x.name = newTree(nnkPostfix, ident"*", n)
result = x
proc main {.tpub.} =
echo "test"
I don't considered unit tests harmful, I use them extensively in Arraymancer but I only tests what is public which is probably about 80% of the code because as a math library, only thing kept private is the boilerplate basically.
Funnily you can find arguments on Google blog that:
The naive alternative to Araq's macro:
proc foo =
echo "test"
proc foo2 =
echo "test2"
when defined(testing):
export foo
export foo2
Thank you for the responses and code, I'm learning a lot about the macro system. Nice idea to use a pragma.
Testing the macro I was getting the compiler error:
t.nim(11, 20) Error: invalid indentation
I used treeRepr in the macro to debug the problem. It turned out I was putting the pragma in the wrong place.
wrong:
proc main {.tpub.} (p: int): string =
right:
proc main(p: int): string {.tpub.} =
If you use the first way, a proc node is passed to the macro as expected but it does not contain any parameters.
@wizzardx
In type declarations, generics are just before the equal sign:
type
Node {.acyclic.} [T]= ref object
left, right: Node[T]
data: T