【问题标题】:Basic unit testing for Fortran in a locked-down environment锁定环境中 Fortran 的基本单元测试
【发布时间】:2015-09-07 09:06:47
【问题描述】:

尝试向大量现有 (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) 中并使用构建脚本自动测试 main 存储在主代码文件中的预处理器分隔符之间。

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

【问题讨论】:

    标签: linux unit-testing testing fortran legacy


    【解决方案1】:

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

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

    我们正在使用 Waf 构建我们的 Fortran 应用程序,它带有一个单元 testing utility itself。现在,我不知道这是否可以供您使用,但唯一的要求是 Python,它应该可以在几乎任何平台上使用。一个缺点是,测试依赖于一个返回码,这在 Fortran 中不容易获得,至少在 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 结尾的所有文件都将被假定为单元测试,我们将尝试构建并运行它们:

    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 中使用了哪些通过:

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

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

    ./waf build --target=aot_table_test
    

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

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

    【讨论】:

    • 感谢您的反馈。不幸的是,这里没有 Waf 选项(令人沮丧的是,我无法从外部源导入代码,而不是依赖问题)。我完全同意你关于解耦和依赖解决是主要挑战的观点——我正在寻找解决方案的原因之一是鼓励团队的其他成员考虑依赖问题;我发现在过去,单元测试是鼓励这种做法的有效方式。看到您如何在并行项目中解决问题非常有趣。干杯
    • 至少,Fortran 2008 标准建议编译器供应商使用停止语句中的错误代码(例如 stop 2)作为退出代码,因此有一种准可移植的方式来返回退出代码。
    • @BálintAradi,感谢您指出,我还没有依赖 F2008 的功能,上次我检查了不同的编译器,它不能正常工作。我会把这个添加到答案中。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-03-24
    • 2010-10-08
    • 1970-01-01
    • 1970-01-01
    • 2021-05-12
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多