Writing Unit Tests for NWChemEx

Within the first party NWChemEx libraries, we aim for extensive unit testing to ensure functionality and correctness. All classes, functions, and modules added to any of the first party libraries will be expected to have corresponding unit tests. Testing of functions (as well as Plugin modules) should minimally ensure that all return routes and errors are checked. Tests for classes should do the same for all member functions, while additionally testing that the state of all instances is consistent at construction and after modifications. Generally, the unit tests should be able to run quickly, and use simplified data with the minimum level of complexity need to ensure completeness in the testing.

The C++ unit tests use the Catch2 framework, while python tests use the unittest framework. Assume the following class and related comparison function are intended to be added to one of the first party libraries:

 1#include <stdexcept>
 2
 3class ToBeTested {
 4private:
 5    using value_type = int;
 6    value_type my_value_;
 7
 8public:
 9    ToBeTested(value_type a_value = 0) : my_value_(a_value) {}
10
11    value_type check_my_value() { return my_value_; }
12
13    void change_my_value(value_type new_value) {
14        if(new_value == 13) throw std::runtime_error("Unlucky Number");
15        my_value_ = new_value;
16    }
17
18    bool operator==(const ToBeTested& rhs) const noexcept {
19        return my_value_ == rhs.my_value_;
20    }
21
22}; // ToBeTested
23
24inline bool operator!=(const ToBeTested& lhs, const ToBeTested& rhs) {
25    return !(lhs == rhs);
26}

An example unit test for the above looks like:

 1#include "to_be_tested.hpp"
 2#include <catch2/catch.hpp>
 3
 4TEST_CASE("ToBeTested") {
 5    auto defaulted  = ToBeTested();
 6    auto with_value = ToBeTested(3);
 7
 8    SECTION("Comparisons") {
 9        SECTION("operator==") {
10            REQUIRE(defaulted == ToBeTested());
11            REQUIRE(with_value == ToBeTested(3));
12            REQUIRE_FALSE(defaulted == with_value);
13        }
14        SECTION("operator!=") {
15            REQUIRE(defaulted != with_value);
16        }
17    }
18
19    SECTION("check_my_value") {
20        REQUIRE(defaulted.check_my_value() == 0);
21        REQUIRE(with_value.check_my_value() == 3);
22    }
23
24    SECTION("change_my_value") {
25        SECTION("Not Unlucky") {
26            defaulted.change_my_value(7);
27            REQUIRE(defaulted.check_my_value() == 7);
28        }
29        SECTION("Unlucky") {
30            REQUIRE_THROWS_AS(defaulted.change_my_value(13),
31                              std::runtime_error);
32        }
33    }
34}