Skip to content

Writing unit tests

Tim Greaves edited this page Jun 18, 2014 · 1 revision

What is a unit test?

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.

Why would you want to?

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.

How do I write one?

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.

How to run them

Run

 make unit test

in the fluidity source directory.

Running a particular test

Make sure you have an up to date libfemtools

 make libfemtools

then run the unittest:

 scripts/runut error_measures/tests/test_whatever -f
Clone this wiki locally