在锁定环境中对Fortran进行基本单元测试

时间:2015-09-07 09:06:47

标签: linux unit-testing testing fortran legacy

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

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

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

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

我想到了几种可能的方法,但实施这些方法可能会遇到技术或管理方面的挑战:

  1. 为每个模块分离测试模块,如下所示,并有一个主测试运行器来调用每个模块中的每个功能

    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)中使用构建脚本自动测试主代码文件中预处理器分隔符之间存储的主要内容。

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

1 个答案:

答案 0 :(得分:2)

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

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

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

    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结尾的所有文件都将被假定为单元测试,我们将尝试构建并运行它们: / p>

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中定义的单位测试。在wscript via:

中使用了哪些内容
from waflib.extras import utest_results
utest_results.utests(bld, 'aotus')

然后,也可以仅从单元测试中构建子集,例如通过运行

./waf build --target=aot_table_test
在Aotus。我们的测试覆盖率有点微薄,但我认为这些基础设施展会实际上相当不错。测试可以简单地利用项目中的所有模块,并且可以轻松编译而不需要进一步操作。

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