haskell 我对失败的案例进行了“阴谋测试”,但它总是通过

8xiog9wr  于 2022-12-13  发布在  其他
关注(0)|答案(2)|浏览(151)

我想测试一个函数,并且希望这个测试失败。在使用cabal init启动项目后,我在app目录下的Main.hs文件中创建了findOddInt函数:

module Main where

findOddInt :: [Int] -> Int
findOddInt (x:xs) = undefined

main :: IO ()
main = putStrLn "Hello, Haskell!"

我创建了一个名为tests的目录,并创建了FindOddInt.hs文件:

module FindOddInt ( main ) where
import Main hiding ( main )
import Test.HUnit
import qualified System.Exit as Exit

test1 :: Test
test1 = TestCase (assertEqual "should return an odd integer" 3 (findOddInt [0, 1, 0, 1, 0]))

tests :: Test
tests = TestList [TestLabel "test1" test1]

main :: IO ()
main = do
    result <- runTestTT tests
    if failures result > 0 then Exit.exitFailure else Exit.exitSuccess

我的.cabal文件如下:

cabal-version:      2.4
name:               find-odd-int
version:            0.1.0.0

-- A short (one-line) description of the package.
-- synopsis:

-- A longer description of the package.
-- description:

-- A URL where users can report bugs.
-- bug-reports:

-- The license under which the package is released.
-- license:
author:             André Ferreira
maintainer:         andresouzafe@gmail.com

-- A copyright notice.
-- copyright:
-- category:
extra-source-files: CHANGELOG.md

executable find-odd-int
    main-is:          Main.hs

    -- Modules included in this executable, other than Main.
    -- other-modules:

    -- LANGUAGE extensions used by modules in this package.
    -- other-extensions:
    build-depends:    base ^>=4.14.3.0
    hs-source-dirs:   app
    default-language: Haskell2010

test-suite tests
    type: exitcode-stdio-1.0
    main-is: FindOddIntTest.hs
    build-depends: base ^>=4.14, HUnit ^>=1.6
    hs-source-dirs: app, tests
    other-modules: Main
    default-language: Haskell2010

设置好后,我运行cabal configure --enable-tests && cabal test并得到输出:

Build profile: -w ghc-8.10.7 -O1
In order, the following will be built (use -v for more details):
 - find-odd-int-0.1.0.0 (test:tests) (file app/Main.hs changed)
Preprocessing test suite 'tests' for find-odd-int-0.1.0.0..
Building test suite 'tests' for find-odd-int-0.1.0.0..
[1 of 2] Compiling Main             ( app/Main.hs, /home/asf/Projects/codewars-exercises/haskell/6-kyu/find-odd-int/dist-newstyle/build/x86_64-linux/ghc-8.10.7/find-odd-int-0.1.0.0/t/tests/build/tests/tests-tmp/Main.o )
[2 of 2] Compiling FindOddInt       ( tests/FindOddIntTest.hs, /home/asf/Projects/codewars-exercises/haskell/6-kyu/find-odd-int/dist-newstyle/build/x86_64-linux/ghc-8.10.7/find-odd-int-0.1.0.0/t/tests/build/tests/tests-tmp/FindOddInt.o ) [Main changed]
Linking /home/asf/Projects/codewars-exercises/haskell/6-kyu/find-odd-int/dist-newstyle/build/x86_64-linux/ghc-8.10.7/find-odd-int-0.1.0.0/t/tests/build/tests/tests ...
Running 1 test suites...
Test suite tests: RUNNING...
Test suite tests: PASS
Test suite logged to:
/home/asf/Projects/codewars-exercises/haskell/6-kyu/find-odd-int/dist-newstyle/build/x86_64-linux/ghc-8.10.7/find-odd-int-0.1.0.0/t/tests/test/find-odd-int-0.1.0.0-tests.log
1 of 1 test suites (1 of 1 test cases) passed.

我已经看了下面的帖子:

这些帖子不包括我的情况。任何帮助都是感激的。

o4tp2gmn

o4tp2gmn1#

您将可执行文件的源代码作为测试套件(hs-source-dirs)的一部分包含在内,这会使编译器感到困惑。当编译测试和常规可执行文件时,GHC会在一个名为Main的模块中查找main,在本例中是app/Main.hs,它什么也不做,您的测试模块被编译,但实际上并没有被使用。

  • 不要把app放在测试套件的hs-source-dirs中。更一般地说,不要在多个组件(库、可执行、测试或基准套件)中包含一个目录,除非你知道你在做什么。如果你需要重用代码,你可以把它放在一个库中,让可执行和测试套件依赖它。
  • 您放在.cabal文件中main-is:下的文件应该包含module Main where或不包含模块行。文件名可以是任何内容,但为了避免与库模块混淆,最好使用小写名称。

如果该模块不会被另一个模块(例如Main)导入,那么您可以自由地为它使用任何文件名。
--- https://downloads.haskell.org/ghc/latest/docs/users_guide/separate_compilation.html

ovfsdjhp

ovfsdjhp2#

您试图将两个不同的模块声明为Main,这是不一致的--为了找到main入口点,您希望将FindOddInt.hs视为Main,但为了导入内容,您希望将Main.hs视为Main
在你的阴谋集团档案里,你有:

test-suite tests
    type: exitcode-stdio-1.0
    main-is: FindOddInt.hs

这意味着Main模块 * 应该 * 在一个名为FindOddInt.hs的文件中(在其中一个hs-source-dirs中)。然而,在FindOddInt.hs本身中,您有:

module FindOddInt ( main ) where
import Main hiding ( main )

所以你说这个文件不是Main模块,它是module FindOddInt。更糟糕的是,你实际上是从另一个名为Main的模块导入的(编译器必须按照通常的模块到文件名的约定来找到它,所以它会在任何hs-source-dirs中寻找Main.hs文件)。
显然,编译器最终使用Main.hs文件作为Main模块的定义,有效地覆盖了cabal文件中的main-is: FindOddInt.hs配置。这意味着您的测试套件只运行putStrLn "Hello, Haskell!",当然会成功。您可以通过运行cabal test --test-show-details=direct来确认这一点。以便显示成功测试和失败测试的输出。

Build profile: -w ghc-9.0.2 -O1
In order, the following will be built (use -v for more details):
 - find-odd-int-0.1.0.0 (test:tests) (first run)
Preprocessing test suite 'tests' for find-odd-int-0.1.0.0..
Building test suite 'tests' for find-odd-int-0.1.0.0..
Running 1 test suites...
Test suite tests: RUNNING...
Hello, Haskell!
Test suite tests: PASS
Test suite logged to:
/tmp/scratch/dist-newstyle/build/x86_64-linux/ghc-9.0.2/find-odd-int-0.1.0.0/t/tests/test/find-odd-int-0.1.0.0-tests.log
1 of 1 test suites (1 of 1 test cases) passed.

你可以看到“你好, haskell ”被印在那里。
如果你改变你的FindOddInt.hs文件,使其头文件为module Main(因为你的cabal文件说那是Main模块),那么你会得到这个错误:

<no location info>: error:
    module ‘main:Main’ is defined in multiple files: tests/FindOddInt.hs
                                                     tests/FindOddInt.hs

这是因为你的other-modules: Main告诉它再次寻找Main模块。

Building test suite 'tests' for find-odd-int-0.1.0.0..
Module imports form a cycle:
  module ‘Main’ (tests/FindOddInt.hs) imports itself

这是因为import Main现在是针对FindOddInt.hs而不是Main.hs进行解析的。
基本上,没有办法让这个工作的方式,你似乎希望它。它可以说是一个bug在阴谋集团和/或GHC,你原来的配置编译在所有;它可能会报告一个关于cabal文件的main-is和引用文件自己的decaled模块名不匹配的错误(如果不是,它使用 * 另一个 * 文件作为Main而不是那个文件,这是非常令人惊讶的)。FindOddInt.hs要么是Main(在这种情况下,您不能从另一个Main导入内容),要么不是。
根据我的经验,从Main导入代码并不是一个好主意。Main的源文件只能包含在一个应用程序中,所以只要你想要两个应用程序入口点(比如你的普通可执行文件和一个测试套件),任何你想在它们之间共享的代码都不能在Main中。
对于我的编码实践来说,这意味着我只在Main中放置了非常少的代码,我不打算用库级测试套件测试这些代码(我可能会对整个二进制代码做进一步的端到端测试,但那是不同的)。我的实际app文件夹通常只有一个非常小的Main.hs,它将main的定义缝合在一起,而main只是组合了从库中导入的内容(有时甚至只是main = SomeModule.realMain,如果我想在realMain上运行库测试的话)。
我也同意夏立耀的建议,多个组件的hs-source-dirs中不要放同一个文件夹,这样做是明智的 * 可能 *(并且是一个选项,用于为您的测试套件提供对库不公开的模块的访问),但它也有缺点,即使它不会导致这样的错误(主要是你浪费时间为每个组件编译文件多次,而不是编译一次然后链接它们)每个组件一个文件夹是最简单也是最容易的构建项目的方法,除非你有很好的理由不这样做。
就我自己而言,我不喜欢给MainMain.hs取不同的名字,因此我认为,严格遵循通常的模块命名约定并让Main.hs文件名被Main模块占用可以避免混淆。如果您这样做并尝试从Main导入Main.hs,您可能会发现正在发生什么(编译器肯定会发现两个声称为Main的文件,将Main放入一个名称不能导入的文件中并没有额外的安全性;它只是强制执行了“不要从Main导入“的规则,而您最终必须坚持这一规则。这使得main-is: Main.hs成为一个毫无意义的样板文件,但至少您不必考虑它。

相关问题