File: Testing.rst

package info (click to toggle)
actor-framework 0.17.6-3.2
  • links: PTS
  • area: main
  • in suites: forky, sid
  • size: 9,008 kB
  • sloc: cpp: 77,684; sh: 674; python: 309; makefile: 13
file content (142 lines) | stat: -rw-r--r-- 4,921 bytes parent folder | download | duplicates (4)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
.. _testing:

Testing
=======

CAF comes with built-in support for writing unit tests in a domain-specific
language (DSL). The API looks similar to well-known testing frameworks such as
Boost.Test and Catch but adds CAF-specific macros for testing messaging between
actors.

Our design leverages four main concepts:

* **Checks** represent single boolean expressions.
* **Tests** contain one or more checks.
* **Fixtures** equip tests with a fixed data environment.
* **Suites** group tests together.

The following code illustrates a very basic test case that captures the four
main concepts described above.

.. code-block:: C++

   // Adds all tests in this compilation unit to the suite "math".
   #define CAF_SUITE math

   // Pulls in all the necessary macros.
   #include "caf/test/dsl.hpp"

   namespace {

   struct fixture {};

   } // namespace

   // Makes all members of `fixture` available to tests in the scope.
   CAF_TEST_FIXTURE_SCOPE(math_tests, fixture)

   // Implements our first test.
   CAF_TEST(divide) {
     CAF_CHECK(1 / 1 == 0); // fails
     CAF_CHECK(2 / 2 == 1); // passes
     CAF_REQUIRE(3 + 3 == 5); // fails and aborts test execution
     CAF_CHECK(4 - 4 == 0); // unreachable due to previous requirement error
   }

   CAF_TEST_FIXTURE_SCOPE_END()

The code above highlights the two basic macros ``CAF_CHECK`` and
``CAF_REQUIRE``. The former reports failed checks, but allows the test
to continue on error. The latter stops test execution if the boolean expression
evaluates to false.

The third macro worth mentioning is ``CAF_FAIL``. It unconditionally
stops test execution with an error message. This is particularly useful for
stopping program execution after receiving unexpected messages, as we will see
later.

Testing Actors
--------------

The following example illustrates how to add an actor system as well as a
scoped actor to fixtures. This allows spawning of and interacting with actors
in a similar way regular programs would. Except that we are using macros such
as ``CAF_CHECK`` and provide tests rather than implementing a
``caf_main``.

.. code-block:: C++

   namespace {

   struct fixture {
     caf::actor_system_config cfg;
     caf::actor_system sys;
     caf::scoped_actor self;

     fixture() : sys(cfg), self(sys) {
       // nop
     }
   };

   caf::behavior adder() {
     return {
       [=](int x, int y) {
         return x + y;
       }
     };
   }

   } // namespace

   CAF_TEST_FIXTURE_SCOPE(actor_tests, fixture)

   CAF_TEST(simple actor test) {
     // Our Actor-Under-Test.
     auto aut = self->spawn(adder);
     self->request(aut, caf::infinite, 3, 4).receive(
       [=](int r) {
         CAF_CHECK(r == 7);
       },
       [&](caf::error& err) {
         // Must not happen, stop test.
         CAF_FAIL(err);
       });
   }

   CAF_TEST_FIXTURE_SCOPE_END()

The example above works, but suffers from several issues:

* Significant amount of boilerplate code.
* Using a scoped actor as illustrated above can only test one actor at a  time. However, messages between other actors are invisible to us.
* CAF runs actors in a thread pool by default. The resulting nondeterminism  makes triggering reliable ordering of messages near impossible. Further,  forcing timeouts to test error handling code is even harder.

Deterministic Testing
---------------------

CAF provides a scheduler implementation specifically tailored for writing unit
tests called ``test_coordinator``. It does not start any threads and
instead gives unit tests full control over message dispatching and timeout
management.

To reduce boilerplate code, CAF also provides a fixture template called
``test_coordinator_fixture`` that comes with ready-to-use actor system
(``sys``) and testing scheduler (``sched``). The optional
template parameter allows unit tests to plugin custom actor system
configuration classes.

Using this fixture unlocks three additional macros:

- ``expect`` checks for a single message. The macro verifies the  content types of the message and invokes the necessary member functions on  the test coordinator. Optionally, the macro checks the receiver of the  message and its content. If the expected message does not exist, the test  aborts.
- ``allow`` is similar to ``expect``, but it does not abort  the test if the expected message is missing. This macro returns  ``true`` if the allowed message was delivered, ``false``  otherwise.
- ``disallow`` aborts the test if a particular message was delivered  to an actor.

The following example implements two actors, ``ping`` and ``pong``, that
exchange a configurable amount of messages. The test *three pings* then checks
the contents of each message with ``expect`` and verifies that no additional
messages exist using ``disallow``.

.. literalinclude:: /examples/testing/ping_pong.cpp
   :language: C++
   :start-after: --(rst-ping-pong-begin)--
   :end-before: --(rst-ping-pong-end)--