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:

  1. create a new subdirectory inside tests/ and add a line like the following to the CMakeLists.txt

    add_subdirectory(new_subdir)
    
  2. 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 by make_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. The add_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 example

  3. create 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.