Skip to content

Commit

Permalink
Merge pull request #133 from minijackson/testing-doc
Browse files Browse the repository at this point in the history
Unit testing documentation
  • Loading branch information
stephane-cea authored Jul 30, 2024
2 parents 304ded2 + cafd633 commit 29dea29
Show file tree
Hide file tree
Showing 5 changed files with 275 additions and 6 deletions.
7 changes: 3 additions & 4 deletions docs/ioc/user-guides/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,12 @@ User guides
----

.. toctree::
:maxdepth: 1
:titlesonly:

./private-repo-setup.rst
./flake-registry.rst
./developing-modules.rst
./mrf-devices.rst
./testing/packaging-python-scripts.rst
./testing/index.rst

.. TODO: make a NixOS test user-guide,
and add a toctree for testing-related guides there
.. TODO: make a NixOS test user-guide
14 changes: 14 additions & 0 deletions docs/ioc/user-guides/testing/index.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
Testing
=======

`How-to guides`_ for users of the EPNix project wanting to test EPICS IOCs.

.. _How-to guides: https://diataxis.fr/how-to-guides/

----

.. toctree::
:titlesonly:

./unit-testing.rst
./packaging-python-scripts.rst
256 changes: 256 additions & 0 deletions docs/ioc/user-guides/testing/unit-testing.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,256 @@
Unit testing
============

This document is a user guide for adding unit tests
to your C and C++ functions.

Using the gtest EPICS module
----------------------------

:ref:`pkg-support.gtest` is an EPICS module
that enables you to use `GoogleTest`_ as a framework for unit testing and mocking.

The GoogleTest framework is a well-known framework for writing unit tests in C++.
You can test C functions using GoogleTest,
but the tests definitions must be in C++.

.. _GoogleTest: https://google.github.io/googletest/

.. _is-library:

Functions to test are in a library
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

.. note::
Read this section if your top is an IOC.

If the top you want to test already is a library / a support top,
you can skip this section.

To test your C/C++ functions,
it's best to have a library
containing all the functions you want to test.
This makes it easier to have your functions both in your IOC and in your tests.

For example,
if you have a :file:`myFunctions.cpp`,
don't add it to the sources of your IOC.
Instead,
create a library,
for example ``myLibrary``,
and add your :file:`myFunctions.cpp` to the sources of this library.

Then,
link your IOC to ``myLibrary``.

If you don't have a library,
change your :file:`src/Makefile`
so that it has these lines:

.. code-block:: make
:caption: :file:`src/Makefile`: building a library
LIBRARY += myLibrary
myLibrary_SRCS += myFunctions
# Libraries needed by 'myLibrary'
myLibrary_LIBS += Com
Then make sure your IOC links with ``myLibrary``:

.. code-block:: make
:caption: :file:`src/Makefile`: linking an IOC to a library
:emphasize-lines: 10-11
# ...
#=============================
# Build the IOC application
PROD_IOC += myIoc
# ...
# Link myIoc with myLibrary
myIoc_LIBS += myLibrary_LIBS
Adding gtest to the top
^^^^^^^^^^^^^^^^^^^^^^^

Add :ref:`pkg-support.gtest` to the build environment:

.. code-block:: nix
:caption: :file:`flake.nix`: adding the ``gtest`` support module
:emphasize-lines: 5
# Add one of the supported modules here:
# ---
support.modules = with pkgs.epnix.support; [
# other support modules...
gtest
];
Writing tests
^^^^^^^^^^^^^

To write your unit tests,
create a separate C++ file,
and follow the `GoogleTest documentation`_.

Here is an example of a test:

.. code-block:: cpp
:caption: :file:`myTest.cpp`: example ``gtest`` test definition
#include <gtest/gtest.h>
#include "myLibrary.hpp"
namespace {
class MyTest : public testing::Test {
protected:
MyTest() { /* Set-up work for each test */ }
~MyTest() { /* Clean up work for each test */ }
};
TEST_F(MyTest, myTest1)
{
// Test something
EXPECT_EQ(2 + 2, 4) << "2 * 2 must be equal to 4";
}
TEST_F(MyTest, myTest2)
{
EXPECT_EQ(6 * 7, 42) << "6 * 7 must be equal to 42";
}
} // namespace
.. _GoogleTest documentation: https://google.github.io/googletest/

Adding tests to the build
^^^^^^^^^^^^^^^^^^^^^^^^^

To add your tests to the build,
add these lines to your :file:`src/Makefile`:

.. code-block:: make
:caption: :file:`src/Makefile`: adding ``gtest`` tests
GTESTPROD_HOST += myTest
myTest_SRCS += myTest.cpp
myTest_LIBS += myLibrary
GTESTS += myTest
.. _running-tests:

Running the tests
^^^^^^^^^^^^^^^^^

To run the tests in your development shell,
run:

.. code-block:: bash
make runtests
Running ``nix build`` also runs tests by default.

To turn off tests inside the Nix build,
add this to your configuration:

.. code-block:: nix
:caption: :file:`flake.nix`: turning tests off
:emphasize-lines: 4
# ...
# checks.imports = [./checks/simple.nix];
buildConfig.attrs.doCheck = false;
Using the epics-base facility
-----------------------------

epics-base provides the `epicsUnitTest.h`_ facility for declaring unit tests,
which can be useful if you don't want to import an external EPICS module.

`epicsUnitTest.h`_ is made in C,
but tests can be written in either C or C++.

.. _epicsUnitTest.h: https://github.com/epics-base/epics-base/blob/R7.0.8.1/modules/libcom/src/misc/epicsUnitTest.h

Pre-requisites
^^^^^^^^^^^^^^

Examine gtest's :ref:`is-library`.

Writing tests
^^^^^^^^^^^^^

To write epics-base unit tests,
make sure:

- You include ``epicsUnitTest.h``
- You include ``testMain.h``
and use the ``MAIN`` macro
to define your main function
- Your main function starts with a :samp:`testPlan({n})`,
with *n* being the number of checks that your test will run
- Your main function returns ``testDone()``
- You're using ``testOk``, ``testPass``, and ``testFail`` to add checks

Here is an example test:

.. code-block:: c
:caption: :file:`myTest.cpp`: example epics-base test definition
#include <epicsUnitTest.h>
#include <testMain.h>
#include "myLibrary.hpp"
static void succeed()
{
testPass("No issues succeeding");
}
static void checkAddition(int x)
{
testOk(x + x == 2 * x, "x + x must be equal to 2 * x");
testOk(x + x + x == 3 * x, "x + x + x must be equal to 3 * x");
}
MAIN(myTest)
{
// 1 for succeed,
// 2 for checkAddition which is called 5 times
testPlan(1 + 5 * 2);
succeed();
for(int i = 0; i < 5; ++i) {
checkAddition(i);
}
return testDone();
}
Adding tests to the build
^^^^^^^^^^^^^^^^^^^^^^^^^

To add your tests to the build,
add these lines to your :file:`src/Makefile`:

.. code-block:: make
:caption: :file:`src/Makefile`: adding epics-base tests
TESTPROD_HOST += myTest
myTest_SRCS += myTest.cpp
myTest_LIBS += myLibrary
TESTSCRIPTS_HOST += myTest.t
Running the tests
^^^^^^^^^^^^^^^^^

Examine gtest's :ref:`running-tests`.
2 changes: 1 addition & 1 deletion ioc/modules/common.nix
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ with lib; {
`nixpkgs.overlays` option.
'';
default = pkgs;
defaultText = literalExpression "<the effective used packages>";
defaultText = literalExpression "<the-effective-used-packages>";
readOnly = true;
type = types.attrs;
};
Expand Down
2 changes: 1 addition & 1 deletion nixos/modules/archiver-appliance.nix
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ in {
'';
type = lib.types.path;
default = "${cfg.package}/share/archappl/policies.py";
defaultText = lib.literalExpression "\${cfg.package}/share/archappl/policies.py";
defaultText = lib.literalExpression ''"''${cfg.package}/share/archappl/policies.py"'';
};

ARCHAPPL_SHORT_TERM_FOLDER = lib.mkOption {
Expand Down

0 comments on commit 29dea29

Please sign in to comment.