-
Notifications
You must be signed in to change notification settings - Fork 116
Writing unit tests
Unit testing is a different but related idea to test cases. Test cases test the functionality of fluidity as a whole, passing fluidity input in and examining the simulation output. Unit testing does a similar thing, but at a different level: it passes input into a ''subroutine'', and examines the output.
Unit testing is a very powerful and useful technique for programming. Suppose you're programming without writing unit tests, and you notice that the output of a subroutine is incorrect. The error could potentially be in any one of the subroutines or functions it calls. Writing unit tests is a great way to test whether a function is doing what you think it should, so that you can depend on it in larger, more complex subroutines.
Writing a unit test is basically writing down what you expect a subroutine to do on a given input, and making sure that the subroutine does it. It can also be a useful safeguard against code changes in other parts of the code: if your code uses subroutines written by someone else, if they change something that breaks your code, you (and they) will know about it.
See the wikipedia article at http://en.wikipedia.org/wiki/Unit_testing for more.
Let's take a look at f90modules/tests/test_div.F90.
subroutine test_div
A unit test is a fortran file in /tests. At the moment there are unit tests for f90modules and error_measures, but ideally there will eventually be unit tests for everything. The fortran file contains a subroutine with the same name as the file. This is the subroutine that gets run.
use fields
use field_derivatives
use vtk_interfaces
use state_module
use unittest_tools
implicit none
The unit test can use any modules that are written for fluidity. Here, this unit test is testing the code that computes the divergence of a vector field, so we use field_derivatives where the code lives. We also want to use some other fortran modules for things like reading in a mesh and some helper functions for unit tests.
type(vector_field) :: field
type(scalar_field) :: divergence
type(state_type) :: state
type(mesh_type), pointer :: mesh
type(vector_field), pointer :: positions
logical :: fail
Here we set up some variables for the problem.
interface
function solution(pos)
real, dimension(:) :: pos
real, dimension(size(pos)) :: solution
end function
end interface
call vtk_read_state("data/pseudo2d.vtu", state)
mesh => extract_mesh(state, "Mesh")
positions => extract_vector_field(state, "Coordinate")
call allocate(field, 3, mesh, "Field")
call allocate(divergence, mesh, "Div")
This code snippet sets up the problem to be run. vtk_read_state reads in a mesh and scalar/vector/tensor fields from a vtu file (in this case, tests/data/pseudo2d.vtu). vtk_read_state stores these in a state object, a dictionary of fields. The mesh and position field are extracted from state, and new fields are allocated (to store the vector field we want to take the divergence of, and a scalar field to store the result).
call set_from_function(field, solution, positions)
call div(field, positions, divergence)
Set the field from the function solution, defined below, and call the subroutine to compute the divergence.
call vtk_write_fields("data/div_out", 0, positions, mesh, sfields=(/divergence/), &
& vfields=(/field/))
This code just prints out the vector field and divergence field to a vtu file for visual inspection.
fail = any(divergence%val > 1e-12)
call report_test("[div]", fail, .false., "div(constant) == 0 everywhere, remember?")
Here we fail if the divergence is nonzero, as the vector field is a constant. The call to report_test reports success or failure of a unit test (the test harness then examines the output of the report_test printouts to determine its status). The arguments are
subroutine report_test(name, has_failed, has_warned, long_error_message)
Note that one unit test can report multiple results, by calling report_test multiple times. It will be considered a failure if any of the tests in it fail.
end subroutine test_div
function solution(pos)
real, dimension(:) :: pos
real, dimension(size(pos)) :: solution
real :: x,y,z
x = pos(1); y = pos(2); z = pos(3)
solution = (/1.0, 2.0, 3.0/)
end function solution
Here we define the solution function called above.
Run
make unit test
in the fluidity source directory.
Make sure you have an up to date libfemtools
make libfemtools
then run the unittest:
scripts/runut error_measures/tests/test_whatever -f