Fortran 在锁定环境中的基本单元测试

Basic unit testing for Fortran in a locked-down environment

尝试向大量现有 (Fortran 90) 代码添加一些基本单元测试的明智方法是什么,这些代码仅在锁定系统上开发,没有机会安装任何第 3 方框架。我几乎受限于标准 Linux 工具。目前,代码库使用非常有限的一组测试在整个系统级别进行测试,但这非常耗时(运行 需要几天),因此在开发过程中很少使用

理想情况下希望能够逐步向关键系统添加目标测试,而不是一次尝试就彻底检查整个代码库。

采用下面的示例模块,并假设实现了 Assert in Fortran

中详述的断言类型宏
MODULE foo
    CONTAINS
    FUNCTION bar() RESULT (some_output)
        INTEGER :: some_output
        some_output = 0
    END FUNCTION bar
END MODULE foo

有几种可能的方法spring,但实施这些方法可能存在技术或行政方面的挑战,我不知道:

  1. 每个模块的单独测试模块,如下所示,并有一个主测试运行ner 来调用每个模块中的每个函数

    MODULE foo_test
        CONTAINS
        SUBROUTINE bar_test()
         ! ....
        END SUBROUTINE bar_test()
    END MODULE foo_test
    
  2. 与上述类似的方法,但每个测试都有单独的可执行文件。明显的好处是单一故障不会终止所有测试,但可能更难管理大量测试可执行文件,并且可能需要大量额外代码。

  3. 使用预处理器在每个模块中包含包含测试的主要功能,例如在 gfortran Fortran 90 with C/C++ style macro (e.g. # define SUBNAME(x) s ## x) 中并使用构建脚本自动测试主代码文件中预处理器定界符之间存储的 main。

我已经尝试使用一些现有的 Fortran 框架(如 >Why the unit test frameworks in Fortran rely on Ruby instead of Fortran itself?> 中所述),但对于这个特定项目,我无法在我使用的系统上安装其他工具.

在我看来,断言机制不是 Fortran 单元测试的主要关注点。正如您链接的答案中提到的,Fortran 存在多个单元测试框架,例如 funit 和 FRUIT。

但是,我认为,主要问题是依赖关系的解决。您可能有一个包含许多相互依赖的模块的庞大项目,并且您的测试应该涵盖使用许多其他模块的模块之一。因此,您需要找到这些依赖项并相应地构建单元测试。一切都归结为编译可执行文件,断言的优势非常有限,因为无论如何您都需要定义测试并自己进行比较。

我们正在使用 Waf, which comes with a unit testing utility itself 构建我们的 Fortran 应用程序。现在,我不知道您是否可以使用它,但唯一的要求是 Python,它几乎可以在任何平台上使用。一个缺点是,测试依赖于 return 代码,这不容易从 Fortran 获得,至少在 Fortran 2008 之前不能以可移植的方式获得,它建议在 return 代码中提供停止代码.所以我修改了我们项目中的成功检查。我希望测试写入一些字符串,并在输出中检查它,而不是检查 return 代码:

    def summary(bld):
            """
            Get the test results from last line of output::

                    Fortran applications can not return arbitrary return codes in
                    a standarized way, instead we use the last line of output to
                    decide the outcome of a test: It has to state "PASSED" to count
                    as a successful test.

                    Otherwise it is considered as a failed test. Non-Zero return codes
                    that might still happen are also considered as failures.

                    Display an execution summary:

                    def build(bld):
                            bld(features='cxx cxxprogram test', source='main.c', target='app')
                            from waflib.extras import utest_results
                            bld.add_post_fun(utest_results.summary)
            """
            from waflib import Logs
            import sys

            lst = getattr(bld, 'utest_results', [])

            # Check for the PASSED keyword in the last line of stdout, to
            # decide on the actual success/failure of the test.
            nlst = []
            for (f, code, out, err) in lst:
                    ncode = code
                    if not code:
                            if sys.version_info[0] > 2:
                                    lines = out.decode('ascii').splitlines()
                            else:
                                    lines = out.splitlines()
                            if lines:
                                    ncode = lines[-1].strip() != 'PASSED'
                            else:
                                    ncode = True
                    nlst.append([f, ncode, out, err])
            lst = nlst

我还按照惯例添加测试,在构建脚本中只需要提供一个目录,该目录中以 _test.f90 结尾的所有文件都将被假定为单元测试,我们将尝试构建和 运行他们:

def utests(bld, use, path='utests'):
    """
    Define the unit tests from the programs found in the utests directory.
    """
    from waflib import Options
    for utest in bld.path.ant_glob(path + '/*_test.f90'):
            nprocs = search_procs_in_file(utest.abspath())
            if int(nprocs) > 0:
                    bld(
                        features = 'fc fcprogram test',
                        source = utest,
                        use = use,
                        ut_exec = [Options.options.mpicmd, '-n', nprocs,
                                   utest.change_ext('').abspath()],
                        target = utest.change_ext(''))
            else:
                    bld(
                        features = 'fc fcprogram test',
                        source = utest,
                        use = use,
                        target = utest.change_ext(''))

您可以通过以下方式找到 Aotus library. Which are utilized in the wscript 中定义的单元测试:

from waflib.extras import utest_results
utest_results.utests(bld, 'aotus')

然后也可以只构建单元测试的子集,例如 运行ning

./waf build --target=aot_table_test

在奥图斯。我们的测试范围有点小,但我认为这个基础设施展览会实际上相当不错。一个测试可以简单地利用项目中的所有模块,并且可以轻松地编译它。

现在我不知道这是否适合你,但我会更多地考虑你的测试在你的构建环境中的集成,而不是断言的东西。在每个模块中都有一个测试例程绝对是个好主意,然后可以很容易地从测试程序中调用它。我会尝试针对每个要测试的模块一个可执行文件,其中每个模块当然可以包含多个测试。