MEPP2 Project
Add a new unit test

General introduction

A unit test is a program (contains the main function) which permits to detect code regressions and is a measure of code quality. Unit tests are used in MEPP2 to prevent either compilation or running time issues. A unit test must have a short execution time (generally less than 1 s), and it must validate only one thing (e.g. only one processing filter for a particular data structure). A failed test must return a value different from 0. It can also have an assertion check (assert call) which fails (but assertion failure is only detected in Debug mode). A successful test always returns 0 and has all its assertions (assert calls) which pass. A unit test generally produces an output file (e.g. a mesh or a point cloud file) and then compares this file with the expected result file (which is called reference file when the expected result is different from the input file).

For mesh and point cloud data structures (the most relevant use case), unit tests permit to ensure the proper functioning of some functionalities. For instance, you can ensure the proper file loading, the proper file writing, or the proper execution of a processing filter (processing onto a mesh or a point cloud is called a filter in MEPP2). You can also ensure that some concepts are implemented.

For external libraries, there might be a specific library usage test (it is always the case if the library was compiled by the MEPP team). It ensures that the library includes are found and that the library link edition operates normally under the 3 OS (Windows, Linux, and Mac OS X).

Presentation of testing directory hierarchy

If MEPP2 is your local repository folder, then MEPP2/Testing is the folder that contains the source code of the unit tests and their input and output reference files.

More specifically, the MEPP2/Testing hierarchy is the following:

  • Folder for specific unit tests
  • Folder for generic unit tests
    • To write generic tests (applicable for several data structures): Generic. Note that generic filters for both meshes and point clouds are specified in this folder, while generic filters specific to 2-manifold meshes (resp. point clouds) are specified in the Manifold (resp. PointCloud) subfolder;
    • Concepts checking (compilation checks only): Concepts.
  • Folder for unit test utilities: Utils.
  • Data folders
    • Mesh files, point clouds files, etc.: Data;
    • Reference files for unit test only: RefOutput. Inside it, you will find data structure subfolders for reference files that are only used by a specific data structure.

Utils (relevant functions/tools for unit testing)

The Utils folder contains functions to help for the elaboration of unit tests. It contains:

  • Generic function to retrieve the vertex_descriptor (resp. halfedge_descriptor, edge_descriptor, and face_descriptor) from 1 index (resp. 2 indices, 2 indices, and 1 index).
  • Functions to compare 2 files (duplicate vertices are not properly managed yet): bool identical_text_based_files(std::string filename_a, std::string filename_b, bool skip_mtllib = false) and bool are_meshes_equal(std::string filename_a, std::string filename_b, bool verbose, FloatT threshold, bool relative_threshold). The first function searches for a line difference in the two input text files. The second function uses face descriptions based on vertex indices and searches for either a point coordinate difference or a face description difference. This function does not work with vertex duplicates and only supports OFF files.

    There are 2 use cases:

    • If you have a reference file located in the RefOutput folder (usual case), you can use it at the end of your applied processing to check if the result is stable (are_meshes_equal(result_mesh_file.off, ref_mesh_file.off, verbose, geom_threshold, attr_threshold, relative_thresholds));
    • If you have filter1 and filter2, such as filter2 is the "inverse" processing of filter1: you first create a classic unit test for filter1 (with a reference file), then you make another unit test for filter2 without a reference file from the RefOutput folder: you will apply filter1 and then filter2, you will thus use the input data file (e.g. from the Data folder) as a reference file. For instance, this kind of test is used to test a decompression filter once we have validated its associated compression filter.
  • A CMakeLists.txt to generate an executable for the comparison of 2 OFF files.

How to add a new unit test for a specific data structure (non-generic)

Testing a processing filter with a data structure (either a mesh or a point cloud data structure) consists in writing a program (containing the main function) inside a mesh/point cloud data structure folder (in the MEPP2/Testing folder). As mentioned at the beginning of this page, it is important that the main function returns 0 when the test passes, while two approaches can be used to make the test fails: returning a value different from 0 (which works in Debug and Release) or asserting with an assert function that something is true of false in your code (which works in Debug).

A non-generic unit test permits to test both a data-structure-specific filter and a generic filter with a specific data structure (see next section for the generic case).

For instance, the CGAL specific filter boolean_operations.hpp is tested for the Polyhedron data structure in the CGAL/Polyhedron folder. See test_boolean_operations_polyhedron.cpp.

The last step is to add your unit test to the MEPP2 project in the CMakeLists.txt file present in the data structure folder. For instance, to add a unit test associated with the test_boolean_operations_polyhedron.cpp in the CGAL/Polyhedron you edit this CMakeLists.txt file and add:

#####
add_executable( test_boolean_operations_polyhedron
test_boolean_operations_polyhedron.cpp
)
target_link_libraries( test_boolean_operations_polyhedron
${Boost_LIBRARIES} ${FBX_LIBRARY} ${VTK_LIBRARIES}
)
# test UNION
add_test( Test_boolean_operations_polyhedron_union
test_boolean_operations_polyhedron
"union"
"${TESTING_DATA_DIR}/CubeTriangleFaces.off"
"${TESTING_DATA_DIR}/tetra.off"
"${TESTING_REFOUTPUT_DIR}/CGAL/boolean_operations/mepp1_cube_tetra_union.off"
)
# test INTERSECTION
add_test( Test_boolean_operations_polyhedron_inter
test_boolean_operations_polyhedron
"inter"
"${TESTING_DATA_DIR}/CubeTriangleFaces.off"
"${TESTING_DATA_DIR}/tetra.off"
"${TESTING_REFOUTPUT_DIR}/CGAL/boolean_operations/mepp1_cube_tetra_inter.off"
)
# test SUBTRACTION
add_test( Test_boolean_operations_polyhedron_minus
test_boolean_operations_polyhedron
"minus"
"${TESTING_DATA_DIR}/CubeTriangleFaces.off"
"${TESTING_DATA_DIR}/tetra.off"
"${TESTING_REFOUTPUT_DIR}/CGAL/boolean_operations/mepp1_cube_tetra_minus.off"
)

Note that ${TESTING_DATA_DIR} (resp. ${TESTING_REFOUTPUT_DIR}) refers to the Data (resp. RefOutput) folder.

Note that unit tests can be run in parallel and therefore it is highly recommended that each output file produced by a unit test in the temporary testing directory has a unique name. For instance, the first test (union case) will produce an output file that is named "test_boolean_operations_polyhedron_UNION.off". Some unit tests specify the output file name directly as an argument of the add_test cmake function.

How to add a new unit test for several data structures (generic unit testing)

A generic test is used to test a generic filter.
The example below is given for a 2-manifold generic test. A generic test for point cloud data structures can be implemented in the same way.

in the MEPP2/Testing/CGAL/Surface_mesh/test_curvature_filter_surfacemesh.cpp file. Note that this file includes the data structure headers and then includes the generic test file.

  • Add your unit test in the relevant CMakeLists.txt as explained in the previous section.
Geometry_traits_cgal_surface_mesh.h
main
int main(int argc, const char **argv)
Definition: helloworld_filter_aif.cpp:24
properties_surface_mesh.h
test_curvature_filter.inl
DataStructures_cgal_surface_mesh.h
UNION
@ UNION
Definition: boolops_definitions.hpp:55