Unit Testing
Unit testing is integral to the GEOS development process. While not all components naturally lend themselves to unit testing (for example a physics solver) every effort should be made to write comprehensive quality unit tests.
Each sub-directory in coreComponents
should have a unitTests
directory containing the test sources. Each test consists of a cpp
file whose name begins with test
followed by a name to describe the test. Please read over the LvArray unit test documentation as it gives an intro to the Google Test framework and a set of best practices.
GEOS Specific Recommendations
An informative example is testSinglePhaseBaseKernels
which tests the single phase flow mobility and accumulation kernels on a variety of inputs.
TEST( SinglePhaseBaseKernels, mobility )
{
int constexpr NTEST = 3;
real64 const dens[NTEST] = { 800.0, 1000.0, 1500.0 };
real64 const dDens_dPres[NTEST] = { 1e-5, 1e-10, 0.0 };
real64 const visc[NTEST] = { 5.0, 2.0, 1.0 };
real64 const dVisc_dPres[NTEST] = { 1e-7, 0.0, 0.0 };
for( int i = 0; i < NTEST; ++i )
{
SCOPED_TRACE( "Input # " + std::to_string( i ) );
real64 mob;
real64 dMob_dPres;
MobilityKernel::compute( dens[i], dDens_dPres[i], visc[i], dVisc_dPres[i], mob, dMob_dPres );
// compute etalon
real64 const mob_et = dens[i] / visc[i];
real64 const dMob_dPres_et = mob_et * (dDens_dPres[i] / dens[i] - dVisc_dPres[i] / visc[i]);
EXPECT_DOUBLE_EQ( mob, mob_et );
EXPECT_DOUBLE_EQ( dMob_dPres, dMob_dPres_et );
}
}
[Source: coreComponents/physicsSolvers/fluidFlow/unitTests/testSinglePhaseMobilityKernel.cpp]
What makes this such a good test is that it depends on very little other than kernels themselves. There is no need to involve the data repository or parse an XML file. Sometimes however this is not possible, or at least not without a significant duplication of code. In this case it is better to embed the XML file into the test source as a string instead of creating a separate XML file and passing it to the test as a command line argument or hard coding the path. One example of this is testLaplaceFEM
which tests the laplacian solver. The embedded XML is shown below.
char const * xmlInput =
R"xml(
<Problem>
<Solvers gravityVector="{ 0.0, 0.0, -9.81 }">
<CompositionalMultiphaseFVM name="compflow"
logLevel="0"
discretization="fluidTPFA"
targetRegions="{region}"
temperature="297.15"
useMass="1">
<NonlinearSolverParameters newtonTol="1.0e-6"
newtonMaxIter="2"/>
<LinearSolverParameters solverType="gmres"
krylovTol="1.0e-10"/>
</CompositionalMultiphaseFVM>
</Solvers>
<Mesh>
<InternalMesh name="mesh"
elementTypes="{C3D8}"
xCoords="{0, 3}"
yCoords="{0, 1}"
zCoords="{0, 1}"
nx="{3}"
ny="{1}"
nz="{1}"
cellBlockNames="{cb1}"/>
</Mesh>
<NumericalMethods>
<FiniteVolume>
<TwoPointFluxApproximation name="fluidTPFA"/>
</FiniteVolume>
</NumericalMethods>
<ElementRegions>
<CellElementRegion name="region" cellBlocks="{*}" materialList="{fluid, rock, relperm, cappressure}" />
</ElementRegions>
<Constitutive>
<CompositionalMultiphaseFluid name="fluid"
phaseNames="{oil, gas}"
equationsOfState="{PR, PR}"
componentNames="{N2, C10, C20, H2O}"
componentCriticalPressure="{34e5, 25.3e5, 14.6e5, 220.5e5}"
componentCriticalTemperature="{126.2, 622.0, 782.0, 647.0}"
componentAcentricFactor="{0.04, 0.443, 0.816, 0.344}"
componentMolarWeight="{28e-3, 134e-3, 275e-3, 18e-3}"
componentVolumeShift="{0, 0, 0, 0}"
componentBinaryCoeff="{ {0, 0, 0, 0},
{0, 0, 0, 0},
{0, 0, 0, 0},
{0, 0, 0, 0} }"/>
<CompressibleSolidConstantPermeability name="rock"
solidModelName="nullSolid"
porosityModelName="rockPorosity"
permeabilityModelName="rockPerm"/>
<NullModel name="nullSolid"/>
<PressurePorosity name="rockPorosity"
defaultReferencePorosity="0.05"
referencePressure = "0.0"
compressibility="1.0e-9"/>
<BrooksCoreyRelativePermeability name="relperm"
phaseNames="{oil, gas}"
phaseMinVolumeFraction="{0.1, 0.15}"
phaseRelPermExponent="{2.0, 2.0}"
phaseRelPermMaxValue="{0.8, 0.9}"/>
<BrooksCoreyCapillaryPressure name="cappressure"
phaseNames="{oil, gas}"
phaseMinVolumeFraction="{0.2, 0.05}"
phaseCapPressureExponentInv="{4.25, 3.5}"
phaseEntryPressure="{0., 1e8}"
capPressureEpsilon="0.0"/>
<ConstantPermeability name="rockPerm"
permeabilityComponents="{2.0e-16, 2.0e-16, 2.0e-16}"/>
</Constitutive>
<FieldSpecifications>
<FieldSpecification name="initialPressure"
initialCondition="1"
setNames="{all}"
objectPath="ElementRegions/region/cb1"
fieldName="pressure"
functionName="initialPressureFunc"
scale="5e6"/>
<FieldSpecification name="initialComposition_N2"
initialCondition="1"
setNames="{all}"
objectPath="ElementRegions/region/cb1"
fieldName="globalCompFraction"
component="0"
scale="0.099"/>
<FieldSpecification name="initialComposition_C10"
initialCondition="1"
setNames="{all}"
objectPath="ElementRegions/region/cb1"
fieldName="globalCompFraction"
component="1"
scale="0.3"/>
<FieldSpecification name="initialComposition_C20"
initialCondition="1"
setNames="{all}"
objectPath="ElementRegions/region/cb1"
fieldName="globalCompFraction"
component="2"
scale="0.6"/>
<FieldSpecification name="initialComposition_H20"
initialCondition="1"
setNames="{all}"
objectPath="ElementRegions/region/cb1"
fieldName="globalCompFraction"
component="3"
scale="0.001"/>
</FieldSpecifications>
<Functions>
<TableFunction name="initialPressureFunc"
inputVarNames="{elementCenter}"
coordinates="{0.0, 3.0}"
values="{1.0, 0.5}"/>
</Functions>
</Problem>
)xml";
[Source: coreComponents/physicsSolvers/fluidFlow/unitTests/testCompMultiphaseFlow.cpp]
MPI
Often times it makes sense to write a unit test that is meant to be run with multiple MPI ranks. This can be accomplished by simply adding the NUM_MPI_TASKS
parameter to geos_add_test
in the CMake file. For example
geos_add_test( NAME testWithMPI
COMMAND testWithMPI
NUM_MPI_TASKS ${NUMBER_OF_MPI_TASKS} )
With this addition make test
or calling ctest
directly will run testWithMPI
via something analogous to mpirun -n NUMBER_OF_MPI_TASKS testWithMPI
.