使用test-drive测试你的代码

test-drive库(许可证:MIT或Apache 2.0):https://github.com/fortran-lang/test-drive

在代码开发中,合理的单元测试可以解放自己的双手,让计算机使用事先写好的测试代码自动化验证你的业务代码。

test-drive原为Sebastian Ehlert开发的单元测试库,质量很高,现在已经移交到Fortran-lang组织命名空间下。

简介

test-drive现在由Fortran-lang组织维护开发,它的使用相对简单,巧妙使用回调函数,设计较为合理,且适应于配合fpm使用。

它的应用场景主要是:

  • 函数库的开发(简单);
  • 可执行程序的开发(复杂些)。

另外,Sebastian Ehlert也是一名资深的Fortran开源开发者。

test-drive演示

我们使用test-drive库中README.md的示例,进行演示:

cd workspace  # 切换到你常用的工作区间
fpm new --app test-drive-demo  # 创建fpm项目
cd test-drive-demo && code .   # 切换到`test-drive-demo`文件夹,并使用vs code打开它

我们创建了一个test-drive-demo工程,使用vs code打开了它,我们可以在fpm工程的fpm.toml文件中添加以下语句,以使用test-drive

[dependencies]
test-drive = { git="https://github.com/fortran-lang/test-drive.git" }

test-drive-demo

通过在分类的module里设置相关单元测试,建立一个或多个主测试程序,使用fpm test命令,对业务代码进行单元测试。

单元测试的实现核心是判断,在这里体现为check子例程,当condition为不符合预期的.false.时,程序中断,提示用户,计算结果不符合预期。

这在我们开发和修改业务代码内容时,提供了一个检查预期待的途径,帮助我们节省人力物力。这在代码开发中,也叫做TDD(test-drived developement,测试驱动开发)。

示例代码

主测试程序

program tester
    use, intrinsic :: iso_fortran_env, only: error_unit
    use testdrive, only: run_testsuite, new_testsuite, testsuite_type
    use test_suite1, only: collect_suite1
    implicit none
    integer :: stat, is
    type(testsuite_type), allocatable :: testsuites(:)
    character(len=*), parameter :: fmt = '("#", *(1x, a))'

    stat = 0

    testsuites = [ &
                 new_testsuite("suite1", collect_suite1) &
                 ]

    do is = 1, size(testsuites)
        write (error_unit, fmt) "Testing:", testsuites(is)%name
        call run_testsuite(testsuites(is)%collect, error_unit, stat)
    end do

    if (stat > 0) then
        write (error_unit, '(i0, 1x, a)') stat, "test(s) failed!"
        error stop
    end if

end program tester

单元测试模块

module test_suite1
    use testdrive, only: new_unittest, unittest_type, error_type, check
    implicit none
    private

    public :: collect_suite1

contains

    !> Collect all exported unit tests
    subroutine collect_suite1(testsuite)
        !> Collection of tests
        type(unittest_type), allocatable, intent(out) :: testsuite(:)

        testsuite = [ &
                    new_unittest("valid", test_valid), &
                    new_unittest("invalid", test_invalid, should_fail=.true.) &
                    ]

    end subroutine collect_suite1

    subroutine test_valid(error)
        type(error_type), allocatable, intent(out) :: error
        
        call check(error, 2, 2)
        if (allocated(error)) return
        call check(error, .true.)
        !> 最后一个return可有可无,已经到了最后,程序自己会return的
        if (allocated(error)) return
        
    end subroutine test_valid

    subroutine test_invalid(error)
        type(error_type), allocatable, intent(out) :: error
        
        call check(error, 1, 2)
        if (allocated(error)) return
        call check(error, .false.)
        if (allocated(error)) return
        
    end subroutine test_invalid

end module test_suite1

说明

test-drive设计合理,源码很值得学习。 除了该库以外,还有很多其他优秀的Fortran单元测试库,此处暂不做介绍。