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}
1class ToBeTested():
2
3def __init__(self, a_value = 0):
4 self.__my_value = a_value
5
6def check_my_value(self):
7 return self.__my_value
8
9def change_my_value(self, new_value):
10 if new_value == 13:
11 raise RuntimeError("Unlucky Number")
12 self.__my_value = new_value
13
14def __eq__(self, other):
15 if not isinstance(other, ToBeTested):
16 return NotImplemented
17 return self.__my_value == other.__my_value
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}
1from to_be_tested import ToBeTested
2import unittest
3
4class TestNewClass(unittest.TestCase):
5 def setUp(self):
6 self.defaulted = ToBeTested()
7 self.with_value = ToBeTested(3)
8
9 def test_equality(self):
10 self.assertEqual(self.defaulted, ToBeTested())
11 self.assertEqual(self.with_value, ToBeTested(3))
12 self.assertNotEqual(self.defaulted, self.with_value)
13
14 def test_check_my_value(self):
15 self.assertEqual(self.defaulted.check_my_value(), 0)
16 self.assertEqual(self.with_value.check_my_value(), 3)
17
18 def test_change_my_value(self):
19 self.defaulted.change_my_value(7)
20 self.assertEqual(self.defaulted.check_my_value(), 7)
21
22 with self.assertRaises(RuntimeError) as context:
23 self.defaulted.change_my_value(13)
24 self.assertTrue("Unlucky Number" in str(context.exception))