Skip to content

4. Tests ​

A test is a program that stops with an error when a check fails. quad gets two, in test/:

fortran
!< Each rule integrates exactly the polynomials of its degree: trapezoid lines, Simpson cubics.
program test_exactness
!< Each rule integrates exactly the polynomials of its degree: trapezoid lines, Simpson cubics.
use quad_rules,  only : R8P, trapezoid, simpson
use polynomials, only : line, cubic
implicit none
real(R8P), parameter :: tolerance = 1.e-14_R8P !< Rounding allowance.

if (abs(trapezoid(line, 0._R8P, 2._R8P, 3) - 8._R8P) > tolerance) error stop 'trapezoid is not exact on a line'
if (abs(simpson(cubic, 0._R8P, 2._R8P, 2) - 4._R8P) > tolerance) error stop 'simpson is not exact on a cubic'
print '(A)', 'exact on polynomials of its degree'
endprogram test_exactness
fortran
!< Halving the interval width divides the error by 2**p: p = 2 for the trapezoid rule, 4 for Simpson's.
program test_order
!< Halving the interval width divides the error by 2**p: p = 2 for the trapezoid rule, 4 for Simpson's.
use quad_rules, only : R8P, trapezoid, simpson
use integrands, only : sine
implicit none
real(R8P), parameter :: pi = acos(-1._R8P) !< Pi.
real(R8P)            :: p_trapezoid        !< Observed order, trapezoid.
real(R8P)            :: p_simpson          !< Observed order, Simpson.

p_trapezoid = log(abs(trapezoid(sine, 0._R8P, pi, 16) - 2._R8P) / abs(trapezoid(sine, 0._R8P, pi, 32) - 2._R8P)) / log(2._R8P)
p_simpson = log(abs(simpson(sine, 0._R8P, pi, 16) - 2._R8P) / abs(simpson(sine, 0._R8P, pi, 32) - 2._R8P)) / log(2._R8P)
print '(A, F6.3, A, F6.3)', 'observed orders: trapezoid ', p_trapezoid, ', simpson ', p_simpson
if (abs(p_trapezoid - 2._R8P) > 0.01_R8P) error stop 'trapezoid is not second order'
if (abs(p_simpson - 4._R8P) > 0.01_R8P) error stop 'simpson is not fourth order'
endprogram test_order
fortran
!< Polynomials whose integrals are known exactly.
module polynomials
!< Polynomials whose integrals are known exactly.
use quad_rules, only : R8P
implicit none
private
public :: line, cubic

contains
   pure function line(x) result(y)
   !< 3x + 1, integral on [0, 2] = 8.
   real(R8P), intent(in) :: x !< Abscissa.
   real(R8P)             :: y !< Value.

   y = 3._R8P * x + 1._R8P
   endfunction line

   pure function cubic(x) result(y)
   !< x**3, integral on [0, 2] = 4.
   real(R8P), intent(in) :: x !< Abscissa.
   real(R8P)             :: y !< Value.

   y = x ** 3
   endfunction cubic
endmodule polynomials
  • test_exactness checks what the rules must get exactly right: the trapezoid rule integrates a line, Simpson's a cubic.
  • test_order checks how fast the error falls: halving the intervals divides it by 4 (trapezoid, second order) and by 16 (Simpson, fourth order).
  • error stop ends the program with a non-zero exit status: that is what makes a test fail.

The tests are built like any program, so the debug mode now lists test/ among its sources:

ini
[modes]
modes = debug release static shared coverage

[debug]
help      = Checks on, no optimisation
compiler  = gnu
cflags    = -c -O0 -g -fcheck=all -Wall
src       = ./src/ ./test/
build_dir = ./build/debug/
target    = src/integrate.F90

[release]
help      = Optimised
compiler  = gnu
cflags    = -c -O2
src       = ./src/
build_dir = ./build/release/
target    = src/integrate.F90

[static]
help        = The library, static
compiler    = gnu
cflags      = -c -O2
src         = ./src/
build_dir   = ./build/static/
target      = src/quad_rules.F90
mklib       = static
output      = libquad.a
track_build = True

[shared]
help        = The library, shared
compiler    = gnu
cflags      = -c -O2 -fPIC
src         = ./src/
build_dir   = ./build/shared/
target      = src/quad_rules.F90
mklib       = shared
output      = libquad.so
track_build = True

[coverage]
help      = Instrumented for coverage
compiler  = gnu
cflags    = -c -O0 -g
coverage  = True
src       = ./src/ ./test/
build_dir = ./build/coverage/
target    = src/integrate.F90

Running the tests ​

Every program in test/ (the default test directory; test_dir in a [test] section changes it) is a test:

console
$ fobis test --list
Discovered tests in 'test':
  test_exactness                 test/test_exactness.F90
  test_order                     test/test_order.F90
console
$ fobis test

Building test suite (2 target(s))...

Builder options
  Directories
    Root of source files directory: "src test"
    Building directory: "build/debug"
    Compiled-objects .o   directory: "build/debug/obj"
    Compiled-objects .mod directory: "build/debug/mod"
  Compiler options
    Vendor: "gnu"
    Compiler command: "gfortran"
    Module directory switch: "-J"
    Compiling flags: "-c -O0 -g -fcheck=all -Wall"
    Linking flags: ""
    Preprocessing flags: ""
    Coverage: False
    Profile: False
  Archiver: ar -rcs
  Ranlib: ranlib
  Preprocessor used: None
  Preprocessor args: 
  Preprocessor output directory: None
  Preprocessor extensions processed: []

Building test/test_exactness.F90
Compiling src/quad_rules.F90 serially
Compiling test/polynomials.F90 serially
Compiling test/test_exactness.F90 serially
Linking ~/quad/build/test/test_exactness
Target test/test_exactness.F90 has been successfully built
Builder options
  Directories
    Root of source files directory: "src test"
    Building directory: "build/debug"
    Compiled-objects .o   directory: "build/debug/obj"
    Compiled-objects .mod directory: "build/debug/mod"
  Compiler options
    Vendor: "gnu"
    Compiler command: "gfortran"
    Module directory switch: "-J"
    Compiling flags: "-c -O0 -g -fcheck=all -Wall"
    Linking flags: ""
    Preprocessing flags: ""
    Coverage: False
    Profile: False
  Archiver: ar -rcs
  Ranlib: ranlib
  Preprocessor used: None
  Preprocessor args: 
  Preprocessor output directory: None
  Preprocessor extensions processed: []

Building test/test_order.F90
Compiling src/integrands.F90 serially
Compiling test/test_order.F90 serially
Linking ~/quad/build/test/test_order
Target test/test_order.F90 has been successfully built

  PASS  test_exactness                 (0.00 s)
  PASS  test_order                     (0.00 s)

  Results: 2 passed, 0 failed, 0 skipped
  Total:   0.00 s

fobis test builds each test with the default mode (here debug, with run-time checks: a test should run in the build that catches the most), runs them, and exits with status 1 when any fails. --filter 'test_ord*' selects tests by name, --suite by a ! fobis: suite=NAME comment in the file.

Doctests ​

A doctest is a check written in a procedure's documentation, next to the code it describes. quad_rules gets one per rule: the code between the fences runs, and its output must be the value after =>:

fortran
   pure function trapezoid(f, a, b, n) result(s)
   !< Composite trapezoidal rule with n intervals.
   !<```fortran
   !< use integrands, only : sine
   !< print '(F8.5)', trapezoid(sine, 0._R8P, acos(-1._R8P), 64)
   !<```
   !=> 1.99960 <<<
console
$ fobis doctests --quiet
executing doctest quad_rules-doctest-1
doctest passed
executing doctest quad_rules-doctest-2
doctest passed

FoBiS writes each snippet into a small program that uses the module, builds it, runs it and compares its output with the expected one. The snippet doubles as an example in the documentation the comments generate.

Coverage ​

The coverage mode instruments the build (coverage = True); the tests run in it write the counts that fobis coverage reads, through gcovr (or lcov):

bash
fobis test --mode coverage
console
$ fobis coverage --mode coverage --format text --exclude "test/.*" && cat coverage/coverage.txt
Collecting coverage data from 5 .gcda file(s)...
Using backend: gcovr
(INFO) Reading coverage data...
(INFO) Writing coverage report...

------------------------------------------------------------------------------
                           GCC Code Coverage Report
Directory: .
------------------------------------------------------------------------------
File                                       Lines    Exec  Cover   Missing
------------------------------------------------------------------------------
src/integrands.F90                             3       3   100%
src/quad_rules.F90                            14      14   100%
------------------------------------------------------------------------------
TOTAL                                         17      17   100%
------------------------------------------------------------------------------

--format html (the default) writes a browsable report in coverage/, --fail-under 90 fails a CI job below 90% of the lines.

What you learned

Test programs and fobis test, doctests and fobis doctests, a coverage mode and fobis coverage. Reference: test, doctests, coverage.

Next: 5. Dependencies.