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
    /* pcmsolver_copyright_start */
    /*
     *     PCMSolver, an API for the Polarizable Continuum Model
     *     Copyright (C) 2013-2015 Roberto Di Remigio, Luca Frediani and contributors
     *     
     *     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.github.io/pcmsolver-doc>
     */
    /* pcmsolver_copyright_end */
    
    #include "catch.hpp"
    
    #include <vector>
    #include <cmath>
    
    #include "Config.hpp"
    
    #include <Eigen/Core>
    
    #include "GePolCavity.hpp"
    #include "TestingMolecules.hpp"
    
    TEST_CASE("GePol cavity for a single sphere", "[gepol][gepol_point]")
    {
        double area = 0.4;
        Molecule point = dummy<0>();
        GePolCavity cavity = GePolCavity(point, area, 0.0, 100.0);
        cavity.saveCavity("point.npz");
    
        /*! \class GePolCavity
         *  \test \b GePolCavityTest_size tests GePol cavity size for a point charge
         */
        SECTION("Test size")
        {
            int size = 32;
            size_t actualSize = cavity.size();
            REQUIRE(size == actualSize);
        }
    
        /*! \class GePolCavity
         *  \test \b GePolCavityTest_area tests GePol cavity surface area for a point charge
         */
        SECTION("Test surface area")
        {
            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
         */
        SECTION("Test volume")
        {
            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 ( size_t 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.