Commit d4d9932d authored by Lukas Riedel's avatar Lukas Riedel

Merge branch 'google-test-for-unit-tests' into 'master'

Google test for unit tests

Closes #152

See merge request !159
parents 272e78ff 8dc33b75
[submodule "plugins/vendor/spdlog"]
path = plugins/vendor/spdlog
url = https://github.com/gabime/spdlog.git
[submodule "plugins/vendor/googletest"]
path = plugins/vendor/googletest
url = https://github.com/google/googletest.git
......@@ -43,6 +43,8 @@
* Linear interpolator for initial conditions and scaling fields !145, !156
* Parameterizations for hydrodynamic dispersion in solute transport !141
* Generic Python VTK file reader !143, !150
* [Google Test](https://github.com/google/googletest) unit test framework
as Git Submodule !159
### Changed
* `Simulation` is renamed `RichardsSimulation` and moved to
......@@ -149,6 +151,7 @@
* Switch to stable `dune-randomfield` release branch !151, !153
* System tests for executing `dorie pfg` module !153
* Finite volume solver for the Richards equation !132
* `SimulationBase` unit test now uses Google Test !159
### Fixed
* Solver in `RichardsSimulation` was using the wrong time variable.
......
......@@ -48,7 +48,7 @@ add_subdirectory("python")
add_subdirectory("doc")
add_subdirectory("dune")
add_subdirectory("lib")
if(dune-testtools_FOUND)
if(DORIE_TESTING)
add_subdirectory("test")
endif()
......
......@@ -77,6 +77,7 @@ by CI tests.
| SuperLU | 5.2 |
| [yaml-cpp](https://github.com/jbeder/yaml-cpp) | >= 5.2.0 |
| [spdlog](https://github.com/gabime/spdlog) | 1.1.0 | Included as Git Submodule
| [Google Test](https://github.com/google/googletest) | `HEAD` | Included as Git Submodule
| [muparser](http://beltoforion.de/article.php?a=muparser) | master |
| [VTK](https://vtk.org/) | >= 7.1.1 | For the Python module only
| [dune-common](https://gitlab.dune-project.org/core/dune-common) | releases/2.6
......
......@@ -47,5 +47,12 @@ message (STATUS "DUNE Libraries: ${DUNE_LIBS}")
# Remove CMake policy stack
cmake_policy(POP)
# Add DORiE testing functions
include(DorieTesting)
# Check if testing is enabled
if (dune-testtools_FOUND)
message(STATUS "Testing enabled: dune-testtools found.")
set(DORIE_TESTING TRUE)
# include the DORiE testing macros
include(DorieTesting)
else()
message(STATUS "Testing disabled: dune-testtools not found.")
endif()
......@@ -48,6 +48,12 @@ endfunction()
# The target this test applies to. This is only required if no SOURCES
# are specified.
#
# .. cmake_param:: CUSTOM_MAIN
# :option:
#
# Write a custom `main()` function for the unit test executables instead
# of generating a default one automatically.
#
# This function serves as wrapper around the function `dune_add_test` which
# registers test for existing targets or adds new test executables from the
# given source files. This function additionally registers the tests as unit
......@@ -61,7 +67,8 @@ endfunction()
#
function(dorie_add_unit_test)
set(SINGLE NAME TARGET)
cmake_parse_arguments(UNIT_TEST "" "${SINGLE}" "" ${ARGN})
set(OPTION CUSTOM_MAIN)
cmake_parse_arguments(UNIT_TEST "${OPTION}" "${SINGLE}" "" ${ARGN})
# use name prefix for test
if(NOT UNIT_TEST_NAME)
......@@ -81,7 +88,14 @@ function(dorie_add_unit_test)
# add to build target and employ compile options
target_link_libraries(${UNIT_TEST_TARGET}
muparser::muparser hdf5 yaml-cpp spdlog)
add_coverage_links(${UNIT_TEST_TARGET})
# add_coverage_links(${UNIT_TEST_TARGET})
if (UNIT_TEST_CUSTOM_MAIN)
target_link_libraries(${UNIT_TEST_TARGET} gtest)
else ()
target_link_libraries(${UNIT_TEST_TARGET} gtest_main)
endif()
add_dependencies(build_unit_tests ${UNIT_TEST_TARGET})
endfunction()
......
add_subdirectory("common")
add_subdirectory("model")
if(dune-testtools_FOUND)
if(DORIE_TESTING)
add_subdirectory("test")
endif()
......
......@@ -270,4 +270,109 @@
local operator convention.
@}
@defgroup UnitTests Unit Tests
@{
@ingroup Common
@brief Information on the unit testing framework used in DORiE
## Overview
DORiE uses and vendors [Google Test](https://github.com/google/googletest)
for managing unit tests. Not all unit tests are currently using it, but all
unit tests to be created in the future will have to.
Unit tests are an essential part of programming for ensuring the software
works as intended on the microscopic level. They help with verifying the
initial implementation, facilitate understanding of the code they test, and
simplify bug fixing. The programming technique of Test-Driven Development
(TDD) even _begins_ with writing a test for the desired functionality before
the actual implementation. This is not required for DORiE, but testing should
always be kept in mind when writing code.
## Build System
Google Test is included into the build system and automatically linked to
executables when using the `dorie_add_unit_test()` CMake function. For most
use cases, it suffices to only write the test functions into the unit test
source file. Google Test then assembles the `main` function of the final
program itself. If a custom `main` is required, pass the `CUSTOM_MAIN` option
to the aforementioned CMake function (this will link to `gtest` instead of
`gtest_main`). Finally, you need to include the Google Test header into your
source file and you're good to go!
## Writing Unit Tests
Google Test offers several macros for writing unit tests.
[_Test cases_](https://github.com/google/googletest/blob/master/googletest/docs/primer.md#simple-tests)
look like regular C++ functions. They are grouped into _test suites_. One
test typically consists of several
[_assertions_](https://github.com/google/googletest/blob/master/googletest/docs/primer.md#assertions)
which can each result in a _success_, a _non-fatal failure_, or a _failure_.
Refer to the Google Test
[nomenclature and basic concepts](https://github.com/google/googletest/blob/master/googletest/docs/primer.md#beware-of-the-nomenclature)
for more information. Source files are structured like this:
@code{.cc}
#include <gtest/gtest.h>
TEST(TestSuite, FirstTestCase)
{
EXPECT_EQ(1, 1); // Success
} // Success!
TEST(TestSuite, SecondTestCase)
{
EXPECT_EQ(1, 2); // Non-fatal failure
ASSERT_TRUE(False); // Fatal failure
EXPECT_TRUE(False); // (never executed)
} // Failure
@endcode
Contrary to a regular program, we want any test case to continue executing as
long as possible after it encountered an error. Doing so it can report any,
not just a single, failing assertion. Any test case in Google Test either has
the form `EXPECT_XXX` or `ASSERT_XXX`. Only the latter aborts the execution
of the current test. It should only be used when further execution does not
make sense, e.g., because it would lead to a severe error.
@code{.cc}
TEST(TestSuite, ThirdTestCase)
{
std::vector vec(10);
std::iota(begin(vec), end(vec), 0);
const auto idx = get_random_index();
ASSERT_LT(idx, vec.size()); // stop here if check fails
EXPECT_EQ(vec[idx], idx); // danger of segfault!
}
@endcode
### Resources
The entire documentation of the Google Test suite is condensed into three
documents:
- <a href="https://github.com/google/googletest/blob/master/googletest/docs/primer.md">Primer</a>
- <a href="https://github.com/google/googletest/blob/master/googletest/docs/advanced.md">Advanced Topics</a>
- <a href="https://github.com/google/googletest/blob/master/googletest/docs/faq.md">FAQ</a>
Here is a non-exhaustive list of shortcuts tocommonly used concepts in unit
testing:
- Organizing [simple tests](https://github.com/google/googletest/blob/master/googletest/docs/primer.md#simple-tests)
- [Assertions](https://github.com/google/googletest/blob/master/googletest/docs/primer.md#assertions)
- [Fixtures](https://github.com/google/googletest/blob/master/googletest/docs/primer.md#test-fixtures-using-the-same-data-configuration-for-multiple-tests):
Class-like structures that are freshly instantiated for every test case
they are used in. Useful for testing classes or running starting tests with
a more complex program state.
- [Parameterized tests](https://github.com/google/googletest/blob/master/googletest/docs/advanced.md#value-parameterized-tests):
Execute the same test multiple times with different parameters.
- [Typed tests](https://github.com/google/googletest/blob/master/googletest/docs/advanced.md#typed-tests):
Execute the same test multiple times with different object types. Useful
for testing templates.
- [Exception assertions](https://github.com/google/googletest/blob/master/googletest/docs/advanced.md#exception-assertions):
Check if a statement throws an exception and optionally check for its type.
@}
**/
#include <gtest/gtest.h>
#ifdef HAVE_CONFIG_H
#include "config.h"
#include "config.h"
#endif
#include <cassert>
// Disable MPI for testing
// In this case, this gives us the FakeMPIHelper instead of the regular one
#ifdef HAVE_MPI
#undef HAVE_MPI
#endif
#include <dune/common/parallel/mpihelper.hh>
#include <dune/common/float_cmp.hh>
#include <dune/common/parallel/mpihelper.hh>
#include <dune/dorie/common/simulation_base.hh>
/// Dummy model for testing.
/** Makes sure that all functions of SimulationBase are properly overridden
* and called.
*/
class DummyModel : public Dune::Dorie::SimulationBase
/// Fixture for all test cases
class ModelFixture : public ::testing::Test
{
public:
DummyModel(Dune::MPIHelper& helper)
: Dune::Dorie::SimulationBase("dummy",
"debug",
helper,
Dune::Dorie::OutputPolicy::None,
Dune::Dorie::AdaptivityPolicy::None)
, _dt(.1)
, _current_time(begin_time())
, _end_time(1.0)
, _grid_was_adapted(false)
, _data_was_written(false)
{}
double begin_time() const override { return 0.; }
double end_time() const override { return _end_time; }
double current_time() const override { return _current_time; }
void suggest_timestep(double dt) override { _dt = std::min(dt,_dt); }
void step() override { _current_time+=_dt; }
/// Do nothing.
void mark_grid () override {};
/// Adapt the grid in our minds.
void adapt_grid () override
{
_grid_was_adapted = true;
}
void write_data() const override
{
this->_log->info("Current time: {}", _current_time);
_data_was_written = true;
}
private:
double _dt;
double _current_time;
public:
double _end_time;
bool _grid_was_adapted;
mutable bool _data_was_written;
protected:
/// Initialize the DUNE MPI Helper from the command line arguments
Dune::MPIHelper& helper = Dune::MPIHelper::instance(0, nullptr);
/// Dummy model for testing only. Implements all pure virtual methods.
class DummyModel : public Dune::Dorie::SimulationBase
{
private:
using Base = Dune::Dorie::SimulationBase;
public:
DummyModel (Dune::MPIHelper& helper) :
Base("dummy",
"debug",
helper,
Dune::Dorie::OutputPolicy::None,
Dune::Dorie::AdaptivityPolicy::None),
_dt(.1),
_current_time(begin_time()),
_end_time(1.0),
_grid_was_adapted(false),
_data_was_written(false)
{ }
double begin_time() const override { return 0.; }
double end_time() const override { return _end_time; }
double current_time() const override { return _current_time; }
void suggest_timestep(double dt) override { _dt = std::min(dt,_dt); }
void step() override { _current_time+=_dt; }
/// Do nothing.
void mark_grid () override {};
/// Adapt the grid in our minds.
void adapt_grid () override
{
_grid_was_adapted = true;
}
void write_data() const override
{
this->_log->info("Current time: {}", _current_time);
_data_was_written = true;
}
private:
double _dt;
double _current_time;
public:
double _end_time;
bool _grid_was_adapted;
mutable bool _data_was_written;
} model = DummyModel(helper);
};
int main(int argc, char **argv)
/// Test if the model is correctly initialized
TEST_F (ModelFixture, Initialize)
{
auto& helper = Dune::MPIHelper::instance(argc, argv);
EXPECT_EQ(model._end_time, 1.0);
EXPECT_FALSE(model._grid_was_adapted);
EXPECT_FALSE(model._data_was_written);
EXPECT_EQ(model.begin_time(), 0.0);
EXPECT_EQ(model.current_time(), 0.0);
EXPECT_EQ(model.output_policy(), Dune::Dorie::OutputPolicy::None);
EXPECT_EQ(model.adaptivity_policy(), Dune::Dorie::AdaptivityPolicy::None);
}
try {
DummyModel model(helper);
/// Test if policies are correctly set
TEST_F (ModelFixture, Policies)
{
const auto adapt_policy = Dune::Dorie::AdaptivityPolicy::WaterFlux;
model.set_policy(adapt_policy);
EXPECT_EQ(model.adaptivity_policy(), adapt_policy);
// run the model without adaptivity
const auto output_policy = Dune::Dorie::OutputPolicy::EndOfRichardsStep;
model.set_policy(output_policy);
EXPECT_EQ(model.output_policy(), output_policy);
}
/// Test if the model correctly performs the run() algorithm
TEST_F (ModelFixture, Run)
{
// perform a step
model.step();
EXPECT_TRUE(Dune::FloatCmp::eq(model.current_time(),
model.begin_time() + 0.1));
// run multiple steps
model.run();
assert(not model._grid_was_adapted);
assert(not model._data_was_written);
assert(Dune::FloatCmp::ge(model.current_time(), model.end_time()));
EXPECT_TRUE(Dune::FloatCmp::eq(model.current_time(),
model.end_time()));
EXPECT_FALSE(model._grid_was_adapted);
EXPECT_FALSE(model._data_was_written);
// now set adaptivity
// set new policies
model.set_policy(Dune::Dorie::AdaptivityPolicy::WaterFlux);
model.set_policy(Dune::Dorie::OutputPolicy::EndOfRichardsStep);
model._end_time += 1.0;
// run multiple steps, check if policies are applied
model.run();
assert(model._grid_was_adapted);
assert(model._data_was_written);
assert(Dune::FloatCmp::ge(model.current_time(), model.end_time()));
} catch (Dune::Exception& e) {
std::cout << e.what() << std::endl;
return 1;
} catch (...) {
std::cerr << "Exception occurred!" << std::endl;
return 1;
}
return 0;
EXPECT_TRUE(Dune::FloatCmp::eq(model.current_time(),
model.end_time()));
EXPECT_TRUE(model._grid_was_adapted);
EXPECT_TRUE(model._data_was_written);
}
......@@ -5,3 +5,7 @@ set(DUNE_REENABLE_ADD_TEST True)
set(SPDLOG_BUILD_TESTING OFF)
set(SPDLOG_BUILD_EXAMPLES OFF)
add_subdirectory(spdlog EXCLUDE_FROM_ALL)
# Include the Google Test lib
set(INSTALL_GTEST OFF)
add_subdirectory(googletest EXCLUDE_FROM_ALL)
......@@ -43,6 +43,7 @@ If you which to pull updates from upstream, enter the submodule and call
Submodules checking out tags instead of branches to not need regular remote
updates. See the list below for the current setup.
| Software | Checkout | Branch? |
| -------- | -------- | ------- |
| spdlog | `v1.1.0` | No |
| Software | Checkout | Branch |
| -------- | -------- | ------ |
| spdlog | `v1.1.0` | None |
| googletest | `834dff3b5279164c65f13cc712ed79dbb5c66950` | `master` |
Subproject commit 834dff3b5279164c65f13cc712ed79dbb5c66950
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment