尽管 CPP 覆盖,hspec 仍无法导入(私有)代码依赖项

hspec failing to import (private) code dependency despite CPP override

假设我有一个这样的 src 文件:

{-# LANGUAGE CPP #-}
module Alphabet (
#ifdef TEST
  alphabet
#endif
) where
  alphabet :: [Char]
  alphabet = "abcdefghijklmnopqrstuvwxyz"

一个 .cabal 文件,如下所示:

name:                Alphabet
version:             0.1.0.0
library
  build-depends:       base >=4.8 && <4.9, containers >=0.5 && <0.6, split >=0.2 && <0.3
  hs-source-dirs:      src
  Exposed-modules:     Alphabet
  default-language:    Haskell2010
test-suite alphabet-test
  ghc-options:         -Wall -Werror
  cpp-options:         -DTEST
  default-extensions:  OverloadedStrings
  type:                exitcode-stdio-1.0
  main-is:             Spec.hs
  hs-source-dirs:      tests
  build-depends:       Alphabet, base >= 4.8 && < 4.9, containers >= 0.5 && <0.6, split >= 0.2 && < 0.3, hspec, QuickCheck
  default-language:    Haskell2010

像这样的主测试文件:

 {-# OPTIONS_GHC -F -pgmF hspec-discover #-}

和一个测试文件,如:

module AphabetSpec (spec) where

  import Test.Hspec
  import Alphabet (alphabet)

  spec :: Spec
  spec = do
    describe "Alphabet.alphabet" $ do
      it "returns the alphabet" $ do
        alphabet `shouldBe` "abcdefghijklmnopqrstuvwxyz"

now running `cabal test`:
cabal test
Preprocessing library Alphabet-0.1.0.0...
In-place registering Alphabet-0.1.0.0...
Preprocessing test suite 'alphabet-test' for Alphabet-0.1.0.0...
[1 of 1] Compiling Main             ( tests/AlphabetSpec.hs, dist/build/alphabet-test/alphabet-test-tmp/AlphabetSpec.o )

tests/AlphabetSpec.hs:4:27:
Module ‘Alphabet’ does not export ‘alphabet’

为什么我的 CPP 没有按预期工作?我该如何解决?

问题只是语法错误。 #ifdef错了,#ifndef对了

Why is my CPP not working as expected?

Two-step 建筑。由于您的测试依赖于库,因此它首先构建。该库没有设置任何 CPP 选项,因此 alphabet 不会导出。

构建测试时,您的库已经编译,alphabet 不会导出。这是关注点分离。

How can I fix it?

有几个技巧可以使用 "hidden"(例如 non-exported)函数。首先,您可以将它们全部放入 .Internal 模块中。这样,想要使用隐藏位的用户可以很容易地做到这一点,但临时用户手头没有太多工具。

处理这个问题的另一种方法是删除测试中的依赖项,而是将 src 目录添加到测试中:

 hs-source-dirs:      tests, src

然而,这也意味着您必须重建整个库以进行测试,但它将启用 CPP。

第三种选择是不测试 alphabet,而是测试依赖它的导出函数的可观察行为。所以不是测试 alphabet,而是测试 filterAlpha:

filterAlpha :: String -> String
filterAlpha = filter (`elem` alphabet)

无论如何你都要测试filterAlpha。如果有很多函数使用 alphabet,如果您不小心更改它,您可能会有一些测试会注意到回归。