libgpl

Links

Programs‎ > ‎

Fruit

Original Fruit

The original "Fruit" fortran unit test framework can be found here.
It is released under BSD licence.
It uses ruby rake as build tool, however, fruit in gpl library uses scons as build tool.


Usage

fruit fortran unit test library
input command
list:      [filename] : list modules and subroutines in the upper directory
get:       fileName routineType routineName : generate module_*.f90
unit:      testName : generate test_testName.f90
gen:       :generate unitTest
clean:
merge:     testModuleFileName routineType routineName targetFileName : merge routine
scons:     :preparations for compilation, make SConstruct file
help:      print assert commands


Standard procedure

1. Go to a directory containing a fortran source code you want to test and make a subdirectory named 'test' or something else.
2. Move to the 'test' directory and type 'fruit list'. You will get lists of fortran modules, subroutines, and functions in the upper directory.
3. Type 'fruit get ../code.f90 module I_want_to_test' and fruit will generate 'module_I_want_to_test.f90' file which contains the module.
4. Type 'fruit unit I_want_to_test' and fruit will generate test_I_want_to_test.f90' file.
5. Edit the test file.
6. Type 'fruit gen' and fruit will generate an executable file 'unitTest'.
7. Run 'unitTest'. Modify 'module_I_want_to_test.f90' file and go to step 5 if you want.
8. Type 'fruit merge module_I_want_to_test.f90 module I_want_to_test ../code.f90'. Fruit will generate a backup of '../code.f90' and change the module.

* You can use subroutine and function instead of module.
* Modules, subroutines, or functions you want to test must end with 'end module', 'end subroutine', or 'end function'.

Unit test subroutines

  assert_true

  assert_true(logical value,optional character message)
        If not true, print message.

  assert_equals

        If value2 is different from value1, print values and message.
        Value1 is the value you want to get.
        Value2 is the value you really got.

      assert_equals(value1, value2, optional character message)
        * value1, value2: integer, logical, character

      assert_equals(value1, value2, errorToleranceRange, optional character message)
        * value1, value2, errorToleranceRange: real, double

      assert_equals(array1,array2,integer arraySize, optional character message)
        *array1, array2: integer, character

      assert_equals(array1, array2, integer arraySize, errorToleranceRange, optional character message)
        *array1, array2, errorToleranceRange: real, double

      assert_equals(integer array1, integer array2, integer dimension1, integer dimension2, optional character message)

Example

*source code
*fruit output

$ ls

Hear is a source code I want to test.
area.f90

$ cat area.f90

The code has a module named 'element_area'. And it has a bug in subroutine 'area_tri'
Modules, subroutines, or functions you want to test must end with 'end module', 'end subroutine', or 'end function'.
module element_area

type rectangle
  real:: width,height
end type
type triangle
  real:: width,height
end type

interface get_area
    module procedure area_rect
    module procedure area_tri
end interface

contains

    subroutine area_rect(rect,area)
    implicit none
    type(rectangle),intent(in):: rect
    real,intent(out):: area
    area = rect%width * rect%height
    end subroutine

    subroutine area_tri(tri,area)
    implicit none
    type(triangle),intent(in):: tri
    real,intent(out):: area
    area = 0.4 * tri%width * tri%height
    end subroutine

end module

$ mkdir test

Make 'test' directory. Unit test codes will be placed in this directory.

$ cd test

Move to the 'test' directory.

$ fruit list

'fruit list' will show you lists of modules, subroutines, and functions in the upper directory.
You can test modules, subroutines, and functions in '../*.f90' or '../*.f' files
* MODULES from ../*.f90
../area.f90:module element_area

* SUBROUTINES from ../*.f90
../area.f90:    subroutine area_rect(rect,area)
../area.f90:    subroutine area_tri(tri,area)

* FUNCTION from ../*.f90

* SUBROUTINES from ../*.f
grep: ../*.f: No such file or directory

* FUNCTION from ../*.f
grep: ../*.f: No such file or directory

$ fruit get ../area.f90 module element_area

'fruit get' will copy the module you want to test from the source code to a 'module_[name].f90' file.
file=../area.f90
RoutineType=module
RoutineName=element_area
generating file module_element_area.f90
writing module element_area to module_element_area.f90

$ cat module_element_area.f90 

'module_[name].f90' file contains the module you want to test.
! generated by fruit get
! Sat Feb 14 10:10:04 2009
module element_area

type rectangle
  real:: width,height
end type
type triangle
  real:: width,height
end type

interface get_area
    module procedure area_rect
    module procedure area_tri
end interface

contains

    subroutine area_rect(rect,area)
    implicit none
    type(rectangle),intent(in):: rect
    real,intent(out):: area
    area = rect%width * rect%height
    end subroutine

    subroutine area_tri(tri,area)
    implicit none
    type(triangle),intent(in):: tri
    real,intent(out):: area
    area = 0.4 * tri%width * tri%height
    end subroutine

end module

$ fruit unit element_area

'fruit unit' generates a 'test_[name].f90' file. This is the main test code.
test module:  element_area
created test file: test_element_area.f90

$ cat test_element_area.f90 

'test_[name].f90' file generated by 'fruit unit' is a skeleton of the unit test.
Test codes should go into subroutine test_*.
'setup' and 'teardown' will be called before and after every 'test_*' call.
!! unit test of module element_area
!! Sat Feb 14 10:12:06 2009
module TEST_element_area
    use fruit
contains

    subroutine setup
    end subroutine setup

    subroutine teardown
    end subroutine teardown


    subroutine test_
    use element_area
    end subroutine

end module TEST_element_area

$ vi test_element_area.f90

Edit 'test_[name].f90' file.

$ cat test_element_area.f90 

This is the edited 'test_[name].f90' file. You can find subroutines and functions used for unit test by typing 'fruit help'.
You can add specification by adding ' character :: spec="specification" '.
!! unit test of module element_area
!! Sat Feb 14 10:12:06 2009
module TEST_element_area
    use fruit
contains

    subroutine setup
    end subroutine setup

    subroutine teardown
    end subroutine teardown


    subroutine test_rectangel
    use element_area
    type(rectangle):: myrect
    real:: area
    real:: tolmin=1.e-4
    myrect%width=5.0
    myrect%height=3.0
    call get_area(myrect,area)
    call assert_equals(15.0,area,tolmin)
    end subroutine

    subroutine test_triangle
    use element_area
    type(triangle):: mytri
    real:: area
    real:: tolmin=1.e-4
    character :: spec='triangle 4*3/2=6'
    mytri%width=4.0
    mytri%height=3.0
    call get_area(mytri,area)
    call assert_equals(6.0,area,tolmin)
    end subroutine

end module TEST_element_area


$ fruit gen

'fruit gen' will automatically generate unit test program 'unitTest'.
(It also generates ''SConstruct', fruit_gen.f90', '*.o', and '*.mod' files. You can delete those files generated by 'fruit gen' by typing 'fruit clean')
generating unitTest
scons: Reading SConscript files ...
module name(s): ['element_area']
test name(s): ['test_element_area']
scons: done reading SConscript files.
scons: Building targets ...
gfortran -o test_element_area.o -c -O2 -I/home/user/lib/gplpython/gpl/f90 test_element_area.f90
gfortran -o fruit_gen.o -c -O2 -I/home/user/lib/gplpython/gpl/f90 fruit_gen.f90
gfortran -o unitTest module_element_area.o test_element_area.o fruit_gen.o -L/home/user/lib/gplpython/gpl/f90 -lfruit
scons: done building targets.

$ ./unitTest 

Run 'unitTest'. It shows the test results. 
 [[[ Test module initialized ]]]

    . : successful assert,   F : failed assert 
  
   .. running test: test_rectangel
.  
   .. running test: test_triangle
F   // Un-satisfied spec:
     -- triangle 4*3/2=6
 


 [[[ FRUIT summary: ]]]

 Some tests failed!

  -- Failed assertion messages:
 
    [test_triangle]: Expected [6.0000000], Got [4.8000002]
 
  -- end of failed assertion messages.

 Total asserts :              2
 Successful    :              1
 Failed        :              1
Successful rate:    50.00%

 Successful asserts / total asserts : [            1 /           2  ]
 Successful cases   / total cases   : [            0 /           0  ]
 
 [[[ End of FRUIT summary ]]]

$ vi module_element_area.f90

Fix the bug in the 'module_[name].f90' file.

$ cat module_element_area.f90 

The bug is fixed now.
! generated by fruit get
! Sat Feb 14 10:10:04 2009
module element_area

type rectangle
  real:: width,height
end type
type triangle
  real:: width,height
end type

interface get_area
    module procedure area_rect
    module procedure area_tri
end interface

contains

    subroutine area_rect(rect,area)
    implicit none
    type(rectangle),intent(in):: rect
    real,intent(out):: area
    area = rect%width * rect%height
    end subroutine

    subroutine area_tri(tri,area)
    implicit none
    type(triangle),intent(in):: tri
    real,intent(out):: area
    area = 0.5 * tri%width * tri%height
    end subroutine

end module

$ fruit gen

Compile 'unitTest' again.
generating unitTest
scons: Reading SConscript files ...
module name(s): ['element_area']
test name(s): ['test_element_area']
scons: done reading SConscript files.
scons: Building targets ...
gfortran -o module_element_area.o -c -O2 -I/home/user/lib/gplpython/gpl/f90 module_element_area.f90
gfortran -o test_element_area.o -c -O2 -I/home/user/lib/gplpython/gpl/f90 test_element_area.f90
gfortran -o fruit_gen.o -c -O2 -I/home/user/lib/gplpython/gpl/f90 fruit_gen.f90
gfortran -o unitTest module_element_area.o test_element_area.o fruit_gen.o -L/home/user/lib/gplpython/gpl/f90 -lfruit
scons: done building targets.

$ ./unitTest 

Every test is successful now.
 [[[ Test module initialized ]]]

    . : successful assert,   F : failed assert 
  
   .. running test: test_rectangel
.  
   .. running test: test_triangle
.

 [[[ FRUIT summary: ]]]

 SUCCESSFUL!

   No messages 
 Total asserts :              2
 Successful    :              2
 Failed        :              0
Successful rate:   100.00%

 Successful asserts / total asserts : [            2 /           2  ]
 Successful cases   / total cases   : [            0 /           0  ]
 
 [[[ End of FRUIT summary ]]]

$ fruit merge module_element_area.f90 module element_area ../area.f90 

Merge the modified module to the original source code.
'fruit merge' backs up the original file and replace the buggy module with the modified module.
FRUIT merge
routine: module element_area
input fileName: module_element_area.f90
target fileName: ../area.f90
number of substitution=1
backup: ../area.f90.bak001

$ cd ..

$ ls

area.f90  area.f90.bak001  test

$ cat area.f90

The module is fine now.
module element_area

type rectangle
  real:: width,height
end type
type triangle
  real:: width,height
end type

interface get_area
    module procedure area_rect
    module procedure area_tri
end interface

contains

    subroutine area_rect(rect,area)
    implicit none
    type(rectangle),intent(in):: rect
    real,intent(out):: area
    area = rect%width * rect%height
    end subroutine

    subroutine area_tri(tri,area)
    implicit none
    type(triangle),intent(in):: tri
    real,intent(out):: area
    area = 0.5 * tri%width * tri%height
    end subroutine

end module