Testing¶
We perform unit testing of our API. The unit testing framework used is Catch The framework provides quite an extensive set of macros to test various data types, it also provides facilities for easily setting up test fixtures. Usage is extremely simple and the documentation is very well written. For a quick primer on how to use Catch refer to: https://github.com/philsquared/Catch/blob/master/docs/tutorial.md The basic idea of unit testing is to test each building block of the code separataly. In our case, the term “building block” is used to mean a class.
To add new tests for your class you have to:
create a new subdirectory inside tests/ and add a line like the following to the CMakeLists.txt
add_subdirectory(new_subdir)
create a CMakeLists.txt inside your new subdirectory. This CMakeLists.txt adds the source for a given unit test to the global
UnitTestsSources
property and notifies CTest that a test with given name is part of the test suite. The generation of the CMakeLists.txt can be managed bymake_cmake_files.py
Python script. This will take care of also setting up CTest labels. This helps in further grouping the tests for our convenience. Catch uses tags to index tests and tags are surrounded by square brackets. The Python script inspects the sources and extracts labels from Catch tags. Theadd_Catch_test
CMake macro takes care of the rest.We require that each source file containing tests follows the naming convention new_subdir_testname and that testname gives some clue to what is being tested. Depending on the execution of tests in a different subdirectory is bad practice. A possible workaround is to add some kind of input file and create a text fixture that sets up the test environment. Have a look in the
tests/input
directory for an examplecreate the .cpp files containing the tests. Use the following template:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125
/** * PCMSolver, an API for the Polarizable Continuum Model * Copyright (C) 2016 Roberto Di Remigio, Luca Frediani and collaborators. * * This file is part of PCMSolver. * * PCMSolver is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * PCMSolver is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with PCMSolver. If not, see <http://www.gnu.org/licenses/>. * * For information on the complete list of contributors to the * PCMSolver API, see: <http://pcmsolver.readthedocs.io/> */ #include "catch.hpp" #include <vector> #include <cmath> #include <Eigen/Core> #include "cavity/GePolCavity.hpp" #include "TestingMolecules.hpp" SCENARIO("GePol cavity for a single sphere", "[gepol][gepol_point]") { GIVEN("A single sphere") { double area = 0.4; double probeRadius = 0.0; double minRadius = 100.0; WHEN("the sphere is obtained from a Molecule object") { Molecule point = dummy<0>(); GePolCavity cavity = GePolCavity(point, area, probeRadius, minRadius, "point"); cavity.saveCavity("point.npz"); /*! \class GePolCavity * \test \b GePolCavityTest_size tests GePol cavity size for a point charge in * C1 symmetry without added spheres */ THEN("the size of the cavity is") { int size = 32; int actualSize = cavity.size(); REQUIRE(size == actualSize); } /*! \class GePolCavity * \test \b GePolCavityTest_area tests GePol cavity surface area for a point * charge in C1 symmetry without added spheres */ AND_THEN("the surface area of the cavity is") { double area = 4.0 * M_PI * pow(1.0, 2); double actualArea = cavity.elementArea().sum(); REQUIRE(area == Approx(actualArea)); } /*! \class GePolCavity * \test \b GePolCavityTest_volume tests GePol cavity volume for a point * charge in C1 symmetry without added spheres */ AND_THEN("the volume of the cavity is") { double volume = 4.0 * M_PI * pow(1.0, 3) / 3.0; Eigen::Matrix3Xd elementCenter = cavity.elementCenter(); Eigen::Matrix3Xd elementNormal = cavity.elementNormal(); double actualVolume = 0; for (int i = 0; i < cavity.size(); ++i) { actualVolume += cavity.elementArea(i) * elementCenter.col(i).dot(elementNormal.col(i)); } actualVolume /= 3; REQUIRE(volume == Approx(actualVolume)); } } } GIVEN("A single sphere") { double area = 0.4; double probeRadius = 0.0; double minRadius = 100.0; WHEN("the sphere is obtained from a Sphere object") { Sphere sph(Eigen::Vector3d::Zero(), 1.0); GePolCavity cavity = GePolCavity(sph, area, probeRadius, minRadius, "point"); /*! \class GePolCavity * \test \b GePolCavitySphereCTORTest_size tests GePol cavity size for a point * charge in C1 symmetry without added spheres */ THEN("the size of the cavity is") { int size = 32; int actualSize = cavity.size(); REQUIRE(size == actualSize); } /*! \class GePolCavity * \test \b GePolCavitySphereCTORTest_area tests GePol cavity surface area for * a point charge in C1 symmetry without added spheres */ AND_THEN("the surface area of the cavity is") { double area = 4.0 * M_PI * pow(1.0, 2); double actualArea = cavity.elementArea().sum(); REQUIRE(area == Approx(actualArea)); } /*! \class GePolCavity * \test \b GePolCavitySphereCTORTest_volume tests GePol cavity volume for a * point charge in C1 symmetry without added spheres */ AND_THEN("the volume of the cavity is") { double volume = 4.0 * M_PI * pow(1.0, 3) / 3.0; Eigen::Matrix3Xd elementCenter = cavity.elementCenter(); Eigen::Matrix3Xd elementNormal = cavity.elementNormal(); double actualVolume = 0; for (int i = 0; i < cavity.size(); ++i) { actualVolume += cavity.elementArea(i) * elementCenter.col(i).dot(elementNormal.col(i)); } actualVolume /= 3; REQUIRE(volume == Approx(actualVolume)); } } } }
In this example we are creating a test fixture. The fixture will instatiate a GePolCavity with fixed parameters. The result is then tested against reference values in the various SECTIONs. It is important to add the documentation lines on top of the tests, to help other developers understand which class is being tested and what parameters are being tested. Within Catch fixtures are created behind the curtains, you do not need to worry about those details. This results in somewhat terser test source files.