4

Assume I have a Haskell module named Foo, defined in src/Foo.hs. Assume also that Foo exports a type Bar.

Now I want to write unit tests for Bar (for the whole Foo module, actually), so I throw a couple of QuickCheck properties into test/FooTest.hs; but hey, now I need to define an Arbitrary instance for Bar.

And there's the rub: in -Wall -Werror mode, ghc requires instance declarations to appear in one of two places: in the same file where the type is defined, or where the class is defined. But I don't want to clutter my Foo module with a build dependency on QuickCheck, and I obviously cannot add an instance of Bar to QuickCheck.

So how do I make my datatype an instance of Arbitrary, for the purpose of unit testing only, without introducing a dependency on QuickCheck for users of my module and without tossing-Wall -Werror out of the window?

Arek' Fu
  • 826
  • 8
  • 24

3 Answers3

6

Within the test suite, create a newtype which wraps Bar and define the Arbitrary instance for the newtype.

danidiaz
  • 26,936
  • 4
  • 45
  • 95
  • Argh, will the boilerplate never end?! ;) – Arek' Fu Jul 05 '16 at 05:04
  • @Arek'Fu The boilerplate might be reduced by exploiting safe coercions as needed. – chi Jul 05 '16 at 18:29
  • @chi, it's still an awful lot of noise, and you'd need to fall back on unsafe coercions for GHC 7.6 if you support that. I don't see how it's worth the trouble for a test suite. – dfeuer Jul 05 '16 at 20:29
4

Try ghc -Wall -Werror -Wno-orphans for the test module.

Not exactly perfect since it will disable the warning for other orphan instances, but I believe it's the closest we can get at the moment.

Having a "suppress this warning in the next line" pragma would also be nice.

chi
  • 111,837
  • 3
  • 133
  • 218
  • Almost all of the arguments for avoiding orphans don't apply to test suite executables. This option is much more attractive to me than using CPP to make my module contain different code when included in the tests than for production builds. – Ben Jul 05 '16 at 21:19
  • @Ben, in some cases that's already necessary. Test code may need access to otherwise non-exported constructors and functions. One option is to export all of those unconditionally, but in a large and mature module that doesn't do so, that requires careful testing for potential performance changes. The inliner treats exported and non-exported bindings differently. – dfeuer Jul 08 '16 at 22:55
1

You can conditionally define a TESTING CPP macro when you're compiling the test suite. This lets you avoid orphans but only incur the dependency when you use it. You can see a similar use of such a macro in the containers package, but that package currently uses orphan instances for the specific purpose of adding Arbitrary instances to the test suite. I may change that soon.

dfeuer
  • 48,079
  • 5
  • 63
  • 167