Can internal code be tested without having to mark the test code as internal?

Viewed 121

I have an F# library with lots of non-public stuff I want to test. Currently all code that is not part of the assembly's public API are marked internal (specifically, it's placed in modules that are marked internal). I use the InternalsVisibleToAttribute to make this code visible to my test assembly. However, in order to get the test assembly to compile, all tests that use internal types in their signature (most of them, since I use FsCheck to auto-generate test inputs) must also be marked internal (this needs to be applied to each function, because internal modules won't be discovered by xunit). Additionally, any types used exclusively for FsCheck generation (e.g. type ValidCustomer = ValidCustomer of Customer where Customer is my internal domain type) also need to be marked internal, and FsCheck seems to get stuck when creating internal types, so the tests don't run.

Is there any way to test internal F# code (from a separate test assembly) without having to mark all tests whose signature depends on internal types as internal? Right now I'm simply leaning towards not making anything internal at all in the original code, but ideally there's a way to have my clean-API cake and eat it too.

1 Answers

I've found that the OO world will generally be very opposed to attempting to test anything internal/private directly.

In the functional world I've seen more of a tendency to just expose public functions not intended for public use so they can be tested. See this comment from Edward Kmett.

When I started writing Haskell I started rethinking the way I used to approach encapsulation and hiding.

...

In general I'm a big fan of exposing all of the salient details, constructors and all for my data types through some kind of .Internal module, even if I want encapsulation and safety in the rest of the API.

...

As a side-effect of that you can then use these newly exposed guts to do nice testing. =)

There's a lot more detail in the original comment, and there is a talk where he discusses this at length but I can't find it now.

You could also give the module a really ugly name like __INTERNAL__ to discourage its use.

Related