1. Stefan Scherfke
  2. pyzmq-article

Commits

Stefan Scherfke  committed 7504f96

Reworked article part 2.

  • Participants
  • Parent commits aa808f7
  • Branches default

Comments (0)

Files changed (4)

File article_1.html

View file
 <p>It also inherits <a class="reference external" href="http://docs.python.org/py3k/library/multiprocessing#process-and-exceptions">multiprocessing.Process</a>
 so that it is easier to spawn it as sub-process. Of course, you can also just
 call its <em>run()</em> method from you <em>main()</em>.</p>
-<div class="highlight"><pre><span class="c"># zmqproc.py</span>
+<div class="highlight"><pre><span class="c"># example_app/base.py</span>
 <span class="kn">import</span> <span class="nn">multiprocessing</span>
 
 <span class="kn">from</span> <span class="nn">zmq.eventloop</span> <span class="kn">import</span> <span class="n">ioloop</span><span class="p">,</span> <span class="n">zmqstream</span>
 
 <span class="sd">                - the wild-card ``*``, meaning all available interfaces,</span>
 <span class="sd">                - the primary IPv4 address assigned to the interface, in its</span>
-<span class="sd">                  numeric representation or</span>
+<span class="sd">                numeric representation or</span>
 <span class="sd">                - the interface name as defined by the operating system.</span>
 
 <span class="sd">                If *bind* is ``False``, *host* may be:</span>
 <p>The <em>PongProc</em> inherits <em>ZmqProcess</em> and is the main class for our process. It
 creates the streams, starts the event loop and dispatches all messages to the
 appropriate handlers:</p>
-<div class="highlight"><pre><span class="c"># pongproc.py</span>
-<span class="kn">from</span> <span class="nn">zmq.utils</span> <span class="kn">import</span> <span class="n">jsonapi</span> <span class="k">as</span> <span class="n">json</span>
+<div class="highlight"><pre><span class="c"># example_app/pongproc.py</span>
 <span class="kn">import</span> <span class="nn">zmq</span>
 
-<span class="kn">import</span> <span class="nn">zmqproc</span>
+<span class="kn">import</span> <span class="nn">base</span>
 
 
 <span class="n">host</span> <span class="o">=</span> <span class="s">&#39;127.0.0.1&#39;</span>
 <span class="n">port</span> <span class="o">=</span> <span class="mi">5678</span>
 
 
-<span class="k">class</span> <span class="nc">PongProc</span><span class="p">(</span><span class="n">zmqproc</span><span class="o">.</span><span class="n">ZmqProcess</span><span class="p">):</span>
+<span class="k">class</span> <span class="nc">PongProc</span><span class="p">(</span><span class="n">base</span><span class="o">.</span><span class="n">ZmqProcess</span><span class="p">):</span>
     <span class="sd">&quot;&quot;&quot;</span>
 <span class="sd">    Main processes for the Ponger. It handles ping requests and sends back</span>
 <span class="sd">    a pong.</span>
 
         <span class="bp">self</span><span class="o">.</span><span class="n">bind_addr</span> <span class="o">=</span> <span class="n">bind_addr</span>
         <span class="bp">self</span><span class="o">.</span><span class="n">rep_stream</span> <span class="o">=</span> <span class="bp">None</span>
-
-        <span class="c"># Make sure this is pickle-able (e.g., not using threads)</span>
-        <span class="c"># or it won&#39;t work on Windows. If it&#39;s not pickle-able, instantiate</span>
-        <span class="c"># it in setup().</span>
         <span class="bp">self</span><span class="o">.</span><span class="n">ping_handler</span> <span class="o">=</span> <span class="n">PingHandler</span><span class="p">()</span>
 
     <span class="k">def</span> <span class="nf">setup</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
         <span class="sd">&quot;&quot;&quot;Sets up PyZMQ and creates all streams.&quot;&quot;&quot;</span>
         <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="n">setup</span><span class="p">()</span>
 
-        <span class="bp">self</span><span class="o">.</span><span class="n">rep_stream</span><span class="p">,</span> <span class="n">_</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">stream</span><span class="p">(</span><span class="n">zmq</span><span class="o">.</span><span class="n">REP</span><span class="p">,</span> <span class="bp">self</span><span class="o">.</span><span class="n">bind_addr</span><span class="p">,</span> <span class="n">bind</span><span class="o">=</span><span class="bp">True</span><span class="p">,</span>
-                <span class="n">callback</span><span class="o">=</span><span class="bp">self</span><span class="o">.</span><span class="n">handle_rep_stream</span><span class="p">)</span>
+        <span class="c"># Create the stream and add the message handler</span>
+        <span class="bp">self</span><span class="o">.</span><span class="n">rep_stream</span><span class="p">,</span> <span class="n">_</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">stream</span><span class="p">(</span><span class="n">zmq</span><span class="o">.</span><span class="n">REP</span><span class="p">,</span> <span class="bp">self</span><span class="o">.</span><span class="n">bind_addr</span><span class="p">,</span> <span class="n">bind</span><span class="o">=</span><span class="bp">True</span><span class="p">)</span>
+        <span class="bp">self</span><span class="o">.</span><span class="n">rep_stream</span><span class="o">.</span><span class="n">on_recv</span><span class="p">(</span><span class="n">RepStreamHandler</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">rep_stream</span><span class="p">,</span> <span class="bp">self</span><span class="o">.</span><span class="n">stop</span><span class="p">,</span>
+                                                 <span class="bp">self</span><span class="o">.</span><span class="n">ping_handler</span><span class="p">))</span>
 
     <span class="k">def</span> <span class="nf">run</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
         <span class="sd">&quot;&quot;&quot;Sets up everything and starts the event loop.&quot;&quot;&quot;</span>
     <span class="k">def</span> <span class="nf">stop</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
         <span class="sd">&quot;&quot;&quot;Stops the event loop.&quot;&quot;&quot;</span>
         <span class="bp">self</span><span class="o">.</span><span class="n">loop</span><span class="o">.</span><span class="n">stop</span><span class="p">()</span>
+</pre></div>
+<p>If you are going to start this process as a sub-process via <em>start</em>, make sure
+everything you instantiate in <em>__init__</em> is pickle-able or it won’t work on
+Windows (Linux and Mac OS X use <em>fork</em> to create a sub-process and <em>fork</em> just
+makes a copy of the main process and gives it a new process ID. <a class="reference external" href="http://docs.python.org/py3k/library/multiprocessing#windows">On Windows</a>, there is no
+<em>fork</em> and the context of your main process is pickled and sent to the
+sub-process).</p>
+<p>In <em>setup</em>, call <tt class="docutils literal"><span class="pre">super().setup()</span></tt> before you create a stream or you won’t
+have a <em>loop</em> instance for them. We call <em>setup</em> from <em>run</em>, because the
+context must be created within the new system process, which wouldn’t be the
+case if we called <em>setup</em> from <em>__init__</em>.</p>
+<p>The <em>stop</em> method is not really necessary in this example, but it can be used
+to send stop messages to sub-processes when the main process terminates and to
+do other kinds of clean-up. You can also execute it if you except
+a <tt class="docutils literal">KeyboardInterrupt</tt> after calling <em>run</em>.</p>
+</div>
+<div class="section" id="messagehandler-the-base-class-for-message-handlers">
+<h2>MessageHandler — The Base Class for Message Handlers</h2>
+<p>A PyZMQ message handler can be any callable that accepts one argument—the list
+of message parts as byte objects. Hence, our <em>MessageHandler</em> class needs to
+implement <em>__call__</em>:</p>
+<div class="highlight"><pre><span class="c"># exmaple_app/base.py</span>
 
-    <span class="k">def</span> <span class="nf">handle_rep_stream</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">msg</span><span class="p">):</span>
+<span class="kn">from</span> <span class="nn">zmq.utils</span> <span class="kn">import</span> <span class="n">jsonapi</span> <span class="k">as</span> <span class="n">json</span>
+
+
+<span class="k">class</span> <span class="nc">MessageHandler</span><span class="p">(</span><span class="nb">object</span><span class="p">):</span>
+    <span class="sd">&quot;&quot;&quot;</span>
+<span class="sd">    Base class for message handlers for a :class:`ZMQProcess`.</span>
+
+<span class="sd">    Inheriting classes only need to implement a handler function for each</span>
+<span class="sd">    message type.</span>
+
+<span class="sd">    &quot;&quot;&quot;</span>
+    <span class="k">def</span> <span class="nf">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">json_load</span><span class="o">=-</span><span class="mi">1</span><span class="p">):</span>
+        <span class="bp">self</span><span class="o">.</span><span class="n">_json_load</span> <span class="o">=</span> <span class="n">json_load</span>
+
+    <span class="k">def</span> <span class="nf">__call__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">msg</span><span class="p">):</span>
         <span class="sd">&quot;&quot;&quot;</span>
-<span class="sd">        Handles messages from a Pinger:</span>
-
-<span class="sd">        *ping*</span>
-<span class="sd">            Send back a pong.</span>
-
-<span class="sd">        *plzdiekthxbye*</span>
-<span class="sd">            Stop the ioloop and exit.</span>
+<span class="sd">        Gets called when a messages is received by the stream this handlers is</span>
+<span class="sd">        registered at. *msg* is a list as return by</span>
+<span class="sd">        :meth:`zmq.core.socket.Socket.recv_multipart`.</span>
 
 <span class="sd">        &quot;&quot;&quot;</span>
-        <span class="n">msg_type</span><span class="p">,</span> <span class="n">data</span> <span class="o">=</span> <span class="n">json</span><span class="o">.</span><span class="n">loads</span><span class="p">(</span><span class="n">msg</span><span class="p">[</span><span class="mi">0</span><span class="p">])</span>
+        <span class="c"># Try to JSON-decode the index &quot;self._json_load&quot; of the message</span>
+        <span class="n">i</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_json_load</span>
+        <span class="n">msg_type</span><span class="p">,</span> <span class="n">data</span> <span class="o">=</span> <span class="n">json</span><span class="o">.</span><span class="n">loads</span><span class="p">(</span><span class="n">msg</span><span class="p">[</span><span class="n">i</span><span class="p">])</span>
+        <span class="n">msg</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">=</span> <span class="n">data</span>
 
-        <span class="k">if</span> <span class="n">msg_type</span> <span class="o">==</span> <span class="s">&#39;ping&#39;</span><span class="p">:</span>
-            <span class="n">rep</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">ping_handler</span><span class="o">.</span><span class="n">make_pong</span><span class="p">(</span><span class="n">data</span><span class="p">)</span>
-            <span class="bp">self</span><span class="o">.</span><span class="n">rep_stream</span><span class="o">.</span><span class="n">send_json</span><span class="p">(</span><span class="n">rep</span><span class="p">)</span>
+        <span class="c"># Get the actual message handler and call it</span>
+        <span class="k">if</span> <span class="n">msg_type</span><span class="o">.</span><span class="n">startswith</span><span class="p">(</span><span class="s">&#39;_&#39;</span><span class="p">):</span>
+            <span class="k">raise</span> <span class="ne">AttributeError</span><span class="p">(</span><span class="s">&#39;</span><span class="si">%s</span><span class="s"> starts with an &quot;_&quot;&#39;</span> <span class="o">%</span> <span class="n">msg_type</span><span class="p">)</span>
 
-        <span class="k">elif</span> <span class="n">msg_type</span> <span class="o">==</span> <span class="s">&#39;plzdiekthxbye&#39;</span><span class="p">:</span>
-            <span class="bp">self</span><span class="o">.</span><span class="n">stop</span><span class="p">()</span>
+        <span class="nb">getattr</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">msg_type</span><span class="p">)(</span><span class="o">*</span><span class="n">msg</span><span class="p">)</span>
+</pre></div>
+<p>As you can see, it’s quite simle. It just tries to JSON-load the index defined
+by <tt class="docutils literal">self._json_load</tt>. We earlier defined, that the first element of the
+JSON-encoded message defines the message type (e.g., <em>ping</em>). If an attribute
+of the same name exists in the inheriting class, it is called with the remainer
+of the message.</p>
+<p>You can also add logging or additional security measures here, but that is not
+necessary here.</p>
+</div>
+<div class="section" id="repstreamhandler-the-concrete-message-handler">
+<h2>RepStreamHandler —&nbsp;The Concrete Message Handler</h2>
+<p>This class inherits the <em>MessageHandler</em> I just showed you and is used in
+<em>PongProc.setup</em>. It defines a handler method for <em>ping</em> messages and the
+<em>plzdiekthxbye</em> stop message. In its <em>__init__</em> it receives references to the
+<em>rep_stream</em>, PongProcs <em>stop</em> method and to the <em>ping_handler</em>, our actual
+application logic:</p>
+<div class="highlight"><pre><span class="c"># example_app/pongproc.py</span>
 
-        <span class="k">else</span><span class="p">:</span>
-            <span class="k">raise</span> <span class="ne">RuntimeError</span><span class="p">(</span><span class="s">&#39;Received unkown message type: </span><span class="si">%s</span><span class="s">&#39;</span> <span class="o">%</span> <span class="n">msg_type</span><span class="p">)</span>
+<span class="k">class</span> <span class="nc">RepStreamHandler</span><span class="p">(</span><span class="n">base</span><span class="o">.</span><span class="n">MessageHandler</span><span class="p">):</span>
+    <span class="sd">&quot;&quot;&quot;Handels messages arrvinge at the PongProc’s REP stream.&quot;&quot;&quot;</span>
+    <span class="k">def</span> <span class="nf">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">rep_stream</span><span class="p">,</span> <span class="n">stop</span><span class="p">,</span> <span class="n">ping_handler</span><span class="p">):</span>
+        <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="n">__init__</span><span class="p">()</span>
+        <span class="bp">self</span><span class="o">.</span><span class="n">_rep_stream</span> <span class="o">=</span> <span class="n">rep_stream</span>
+        <span class="bp">self</span><span class="o">.</span><span class="n">_stop</span> <span class="o">=</span> <span class="n">stop</span>
+        <span class="bp">self</span><span class="o">.</span><span class="n">_ping_handler</span> <span class="o">=</span> <span class="n">ping_handler</span>
+
+    <span class="k">def</span> <span class="nf">ping</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">data</span><span class="p">):</span>
+        <span class="sd">&quot;&quot;&quot;Send back a pong.&quot;&quot;&quot;</span>
+        <span class="n">rep</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_ping_handler</span><span class="o">.</span><span class="n">make_pong</span><span class="p">(</span><span class="n">data</span><span class="p">)</span>
+        <span class="bp">self</span><span class="o">.</span><span class="n">_rep_stream</span><span class="o">.</span><span class="n">send_json</span><span class="p">(</span><span class="n">rep</span><span class="p">)</span>
+
+    <span class="k">def</span> <span class="nf">plzdiekthxbye</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">data</span><span class="p">):</span>
+        <span class="sd">&quot;&quot;&quot;Just calls :meth:`PongProc.stop`.&quot;&quot;&quot;</span>
+        <span class="bp">self</span><span class="o">.</span><span class="n">_stop</span><span class="p">()</span>
 </pre></div>
-<p>There are a couple of things to note here:</p>
-<ul>
-<li><p class="first">I instantiated the <em>PingHandler</em> in the process’ <em>__init__</em> method. If you
-are going to start this process as a sub-process via <em>start</em>, make sure
-everything you instantiate in <em>__init__</em> is pickle-able or it won’t work on
-Windows (Linux and Mac OS X use <em>fork</em> to create a sub-process and <em>fork</em>
-just makes a copy of the main process and gives it a new process ID. <a class="reference external" href="http://docs.python.org/py3k/library/multiprocessing#windows">On
-Windows</a>,
-there is no <em>fork</em> and the context of your main process is pickled and sent
-to the sub-process).</p>
-</li>
-<li><p class="first">In <em>setup</em>, call <tt class="docutils literal"><span class="pre">super().setup()</span></tt> before you create a stream or you
-won’t have a loop instance for them. You don’t call <em>setup</em> in
-the process’ <em>__init__</em>, because the context must be created within the
-new system process. So we call <em>setup</em> in <em>run</em>.</p>
-</li>
-<li><p class="first">The <em>stop</em> method is not really necessary in this example, but it can be used
-to send stop messages to sub-processes when the main process terminates and
-to do other kinds of clean-up. You can also execute it if you except a
-<tt class="docutils literal">KeyboardInterrupt</tt> after calling <em>run</em>.</p>
-</li>
-<li><p class="first"><em>handle_rep_stream</em> is the message dispatcher for the process’ <em>REP</em> stream.
-It parses the message and calls the appropriate handler for that message (or
-raises an error if the message type is invalid). If your <em>if</em> and <em>elif</em>
-statements all do the same, you might consider replacing them with a dict
-that contains the handlers for each message type:</p>
-<div class="highlight"><pre><span class="n">handlers</span> <span class="o">=</span> <span class="p">{</span>
-    <span class="s">&#39;msg&#39;</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">handler_for_msg</span><span class="p">,</span>
-<span class="p">}</span>
-<span class="k">try</span><span class="p">:</span>
-    <span class="n">rep</span> <span class="o">=</span> <span class="n">handlers</span><span class="p">[</span><span class="n">msg_type</span><span class="p">](</span><span class="n">data</span><span class="p">)</span>
-    <span class="bp">self</span><span class="o">.</span><span class="n">rep_stream</span><span class="o">.</span><span class="n">send_multipart</span><span class="p">(</span><span class="n">rep</span><span class="p">)</span>
-<span class="k">except</span> <span class="ne">KeyError</span><span class="p">:</span>
-    <span class="k">raise</span> <span class="ne">RuntimeError</span><span class="p">(</span><span class="s">&#39;Received unknown message.&#39;</span><span class="p">)</span>
-</pre></div>
-</li>
-</ul>
 </div>
 <div class="section" id="pinghandler-the-application-logic">
 <h2>PingHandler – The Application Logic</h2>
 in this example). The <em>make_pong</em> method just gets the number of pings sent
 with the <em>ping</em> message and creates a new <em>pong</em> message. The serialization
 is done by <em>PongProc</em>, so our Handler does not depend on PyZMQ:</p>
-<div class="highlight"><pre><span class="k">class</span> <span class="nc">PingHandler</span><span class="p">(</span><span class="nb">object</span><span class="p">):</span>
+<div class="highlight"><pre><span class="c"># example_app/pongproc.py</span>
+
+<span class="k">class</span> <span class="nc">PingHandler</span><span class="p">(</span><span class="nb">object</span><span class="p">):</span>
 
     <span class="k">def</span> <span class="nf">make_pong</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">num_pings</span><span class="p">):</span>
         <span class="sd">&quot;&quot;&quot;Creates and returns a pong message.&quot;&quot;&quot;</span>

File article_1.txt

View file
 .. code-block:: python
 
     # exmaple_app/base.py
+
     from zmq.utils import jsonapi as json
 
 
 .. code-block:: python
 
     # example_app/pongproc.py
+
     class RepStreamHandler(base.MessageHandler):
         """Handels messages arrvinge at the PongProc’s REP stream."""
         def __init__(self, rep_stream, stop, ping_handler):
 
 .. code-block:: python
 
+    # example_app/pongproc.py
+
     class PingHandler(object):
 
         def make_pong(self, num_pings):

File article_2.html

View file
 platform darwin -- Python 3.2.2 -- pytest-2.2.3
 collected 11 items
 
-<span class="nb">test</span>/test_pongproc.py .......
-<span class="nb">test</span>/test_zmqproc.py ....
+example_app/test/test_base.py ....
+example_app/test/test_pongproc.py .......
 
 <span class="o">================</span> 11 passed in 0.12 <span class="nv">seconds</span> <span class="o">=================</span>
 </pre></div>
 <p>Let’s start with <tt class="docutils literal">ZmqProcess</tt> again. After all, everything else depends on it.
 Testing its <em>setup</em> method is easy. We just check that it creates a <em>context</em>
 and a <em>loop</em>:</p>
-<div class="highlight"><pre><span class="c"># test/test_zmqproc.py</span>
+<div class="highlight"><pre><span class="c"># example_app/test/test_zmqproc.py</span>
 <span class="kn">from</span> <span class="nn">zmq.eventloop</span> <span class="kn">import</span> <span class="n">ioloop</span>
 <span class="kn">import</span> <span class="nn">mock</span>
 <span class="kn">import</span> <span class="nn">pytest</span>
 
 
 <span class="k">class</span> <span class="nc">TestZmqProcess</span><span class="p">(</span><span class="nb">object</span><span class="p">):</span>
-    <span class="sd">&quot;&quot;&quot;Tests for :class:`zmqproc.ZmqProcess`.&quot;&quot;&quot;</span>
+    <span class="sd">&quot;&quot;&quot;Tests for :class:`base.ZmqProcess`.&quot;&quot;&quot;</span>
 
     <span class="k">def</span> <span class="nf">test_setup</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
-        <span class="n">zp</span> <span class="o">=</span> <span class="n">zmqproc</span><span class="o">.</span><span class="n">ZmqProcess</span><span class="p">()</span>
+        <span class="n">zp</span> <span class="o">=</span> <span class="n">base</span><span class="o">.</span><span class="n">ZmqProcess</span><span class="p">()</span>
         <span class="n">zp</span><span class="o">.</span><span class="n">setup</span><span class="p">()</span>
 
         <span class="k">assert</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">zp</span><span class="o">.</span><span class="n">context</span><span class="p">,</span> <span class="n">zmq</span><span class="o">.</span><span class="n">Context</span><span class="p">)</span>
 your test function and a list of values for these arguments. For <em>test_stream</em>,
 I only need a <em>kwargs</em> parameter containing the parameters for the <em>stream</em>
 call:</p>
-<div class="highlight"><pre><span class="c"># test/test_zmqproc.py</span>
+<div class="highlight"><pre><span class="c"># example_app/test/test_zmqproc.py</span>
 
     <span class="nd">@pytest.mark.parametrize</span><span class="p">(</span><span class="s">&#39;kwargs&#39;</span><span class="p">,</span> <span class="p">[</span>
         <span class="nb">dict</span><span class="p">(</span><span class="n">sock_type</span><span class="o">=</span><span class="mi">23</span><span class="p">,</span> <span class="n">addr</span><span class="o">=</span><span class="s">&#39;127.0.0.1:1234&#39;</span><span class="p">,</span> <span class="n">bind</span><span class="o">=</span><span class="bp">True</span><span class="p">,</span>
 <p>The next step is to create an instance of <em>ZmqProcess</em> and patch some of its
 attributes. We also need to set a defined return value for the socket’s
 <em>bind_to_random_port</em> method:</p>
-<div class="highlight"><pre><span class="c"># test/test_zmqproc.py</span>
+<div class="highlight"><pre><span class="c"># example_app/test/test_zmqproc.py</span>
 
-        <span class="n">zp</span> <span class="o">=</span> <span class="n">zmqproc</span><span class="o">.</span><span class="n">ZmqProcess</span><span class="p">()</span>
+        <span class="n">zp</span> <span class="o">=</span> <span class="n">base</span><span class="o">.</span><span class="n">ZmqProcess</span><span class="p">()</span>
 
         <span class="c"># Patch the ZmqProcess instance</span>
         <span class="n">zp</span><span class="o">.</span><span class="n">context</span> <span class="o">=</span> <span class="n">mock</span><span class="o">.</span><span class="n">Mock</span><span class="p">(</span><span class="n">spec_set</span><span class="o">=</span><span class="n">zmq</span><span class="o">.</span><span class="n">Context</span><span class="p">)</span>
 we also uses pytest funcargs (e.g., via the <em>parametrize</em> decorator—I don’t
 know if it’s even possible to uses both, <em>mock.patch</em> as decorator and pytest
 funcargs in one test).</p>
-<div class="highlight"><pre><span class="c"># test/test_zmqproc.py</span>
+<div class="highlight"><pre><span class="c"># example_app/test/test_zmqproc.py</span>
 
         <span class="c"># Patch ZMQStream and start testing</span>
         <span class="k">with</span> <span class="n">mock</span><span class="o">.</span><span class="n">patch</span><span class="p">(</span><span class="s">&#39;zmq.eventloop.zmqstream.ZMQStream&#39;</span><span class="p">)</span> <span class="k">as</span> <span class="n">zmqstream_mock</span><span class="p">:</span>
 </pre></div>
 <p>Finally, we can check the return values of our <em>stream</em> method and it made the
 correct calls to create the stream:</p>
-<div class="highlight"><pre><span class="c"># test/test_zmqproc.py</span>
+<div class="highlight"><pre><span class="c"># example_app/test/test_zmqproc.py</span>
 
             <span class="c"># Assert that the return values are correct</span>
             <span class="k">assert</span> <span class="n">stream</span> <span class="ow">is</span> <span class="n">zmqstream_mock</span><span class="o">.</span><span class="n">return_value</span>
 simply, that assert statements are highlighted but ordinary function calls are
 not. This makes it easier for me to find all assertions in a test.</em></p>
 </div>
+<div class="section" id="messagehandler">
+<h2>MessageHandler</h2>
+<p>The <em>MessageHandler</em> base class has only one methd, <em>__call__</em>, but I split the
+test for it into two methods—one that tests the JSON-loading functionality and
+one that checks if the correct handler method is called:</p>
+<div class="highlight"><pre><span class="c"># example_app/test/test_base.py</span>
+
+<span class="k">class</span> <span class="nc">TestMessageHandler</span><span class="p">(</span><span class="nb">object</span><span class="p">):</span>
+    <span class="sd">&quot;&quot;&quot;Tests for :class:`base.TestMessageHandler`.&quot;&quot;&quot;</span>
+
+    <span class="nd">@pytest.mark.parametrize</span><span class="p">((</span><span class="s">&#39;idx&#39;</span><span class="p">,</span> <span class="s">&#39;msg&#39;</span><span class="p">),</span> <span class="p">[</span>
+        <span class="p">(</span><span class="o">-</span><span class="mi">1</span><span class="p">,</span> <span class="p">[</span><span class="mi">23</span><span class="p">,</span> <span class="n">b</span><span class="s">&#39;[&quot;test&quot;, null]&#39;</span><span class="p">]),</span>
+        <span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="p">[</span><span class="mi">23</span><span class="p">,</span> <span class="n">b</span><span class="s">&#39;[&quot;test&quot;, &quot;spam&quot;]&#39;</span><span class="p">,</span> <span class="mi">42</span><span class="p">]),</span>
+        <span class="p">(</span><span class="ne">TypeError</span><span class="p">,</span> <span class="p">[</span><span class="mi">23</span><span class="p">,</span> <span class="mi">42</span><span class="p">]),</span>
+        <span class="p">(</span><span class="ne">ValueError</span><span class="p">,</span> <span class="p">[</span><span class="mi">23</span><span class="p">,</span> <span class="n">b</span><span class="s">&#39;[&quot;test&quot;]23spam&#39;</span><span class="p">]),</span>
+    <span class="p">])</span>
+    <span class="k">def</span> <span class="nf">test_call_json_load</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">idx</span><span class="p">,</span> <span class="n">msg</span><span class="p">):</span>
+        <span class="n">handler</span> <span class="o">=</span> <span class="n">mock</span><span class="o">.</span><span class="n">Mock</span><span class="p">()</span>
+        <span class="n">mh</span> <span class="o">=</span> <span class="n">base</span><span class="o">.</span><span class="n">MessageHandler</span><span class="p">(</span><span class="n">idx</span> <span class="k">if</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">idx</span><span class="p">,</span> <span class="nb">int</span><span class="p">)</span> <span class="k">else</span> <span class="o">-</span><span class="mi">1</span><span class="p">)</span>
+        <span class="n">mh</span><span class="o">.</span><span class="n">test</span> <span class="o">=</span> <span class="n">handler</span>
+
+        <span class="k">if</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">idx</span><span class="p">,</span> <span class="nb">int</span><span class="p">):</span>
+            <span class="n">mh</span><span class="p">(</span><span class="n">msg</span><span class="p">)</span>
+            <span class="k">assert</span> <span class="n">handler</span><span class="o">.</span><span class="n">call_count</span> <span class="o">==</span> <span class="mi">1</span>
+        <span class="k">else</span><span class="p">:</span>
+            <span class="n">pytest</span><span class="o">.</span><span class="n">raises</span><span class="p">(</span><span class="n">idx</span><span class="p">,</span> <span class="n">mh</span><span class="p">,</span> <span class="n">msg</span><span class="p">)</span>
+
+    <span class="nd">@pytest.mark.parametrize</span><span class="p">((</span><span class="s">&#39;ok&#39;</span><span class="p">,</span> <span class="s">&#39;msg&#39;</span><span class="p">),</span> <span class="p">[</span>
+        <span class="p">(</span><span class="bp">True</span><span class="p">,</span> <span class="p">[</span><span class="mi">23</span><span class="p">,</span> <span class="n">b</span><span class="s">&#39;[&quot;test&quot;, &quot;spam&quot;]&#39;</span><span class="p">,</span> <span class="mi">42</span><span class="p">]),</span>
+        <span class="p">(</span><span class="ne">AttributeError</span><span class="p">,</span> <span class="p">[</span><span class="mi">23</span><span class="p">,</span> <span class="n">b</span><span class="s">&#39;[&quot;_test&quot;, &quot;spam&quot;]&#39;</span><span class="p">,</span> <span class="mi">42</span><span class="p">]),</span>
+        <span class="p">(</span><span class="ne">TypeError</span><span class="p">,</span> <span class="p">[</span><span class="mi">23</span><span class="p">,</span> <span class="n">b</span><span class="s">&#39;[&quot;spam&quot;, &quot;spam&quot;]&#39;</span><span class="p">,</span> <span class="mi">42</span><span class="p">]),</span>
+        <span class="p">(</span><span class="ne">AttributeError</span><span class="p">,</span> <span class="p">[</span><span class="mi">23</span><span class="p">,</span> <span class="n">b</span><span class="s">&#39;[&quot;eggs&quot;, &quot;spam&quot;]&#39;</span><span class="p">,</span> <span class="mi">42</span><span class="p">]),</span>
+    <span class="p">])</span>
+    <span class="k">def</span> <span class="nf">test_call_get_handler</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">ok</span><span class="p">,</span> <span class="n">msg</span><span class="p">):</span>
+        <span class="n">handler</span> <span class="o">=</span> <span class="n">mock</span><span class="o">.</span><span class="n">Mock</span><span class="p">()</span>
+        <span class="n">mh</span> <span class="o">=</span> <span class="n">base</span><span class="o">.</span><span class="n">MessageHandler</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>
+        <span class="n">mh</span><span class="o">.</span><span class="n">test</span> <span class="o">=</span> <span class="n">handler</span>
+        <span class="n">mh</span><span class="o">.</span><span class="n">spam</span> <span class="o">=</span> <span class="s">&#39;spam&#39;</span>
+
+        <span class="k">if</span> <span class="n">ok</span> <span class="ow">is</span> <span class="bp">True</span><span class="p">:</span>
+            <span class="n">mh</span><span class="p">(</span><span class="n">msg</span><span class="p">)</span>
+            <span class="k">assert</span> <span class="n">handler</span><span class="o">.</span><span class="n">call_args</span> <span class="o">==</span> <span class="p">(</span>
+                    <span class="p">(</span><span class="n">msg</span><span class="p">[</span><span class="mi">0</span><span class="p">],</span> <span class="s">&#39;spam&#39;</span><span class="p">,</span> <span class="n">msg</span><span class="p">[</span><span class="mi">2</span><span class="p">]),</span> <span class="p">{})</span>
+        <span class="k">else</span><span class="p">:</span>
+            <span class="n">pytest</span><span class="o">.</span><span class="n">raises</span><span class="p">(</span><span class="n">ok</span><span class="p">,</span> <span class="n">mh</span><span class="p">,</span> <span class="n">msg</span><span class="p">)</span>
+</pre></div>
+</div>
 <div class="section" id="pongproc">
 <h2>PongProc</h2>
 <p>Testing the <em>PongProc</em> is not much different from testing its base class.
 has a <tt class="docutils literal">pp</tt> argument. The tests for <em>setup</em>, <em>run</em> and <em>stop</em> are easy to do.
 We create a few mocks and then ask them if the tested function called them
 correctly:</p>
-<div class="highlight"><pre><span class="c"># test/test_pongproc.py</span>
+<div class="highlight"><pre><span class="c"># example_app/test/test_pongproc.py</span>
 <span class="kn">from</span> <span class="nn">zmq.utils</span> <span class="kn">import</span> <span class="n">jsonapi</span> <span class="k">as</span> <span class="n">json</span>
 <span class="kn">import</span> <span class="nn">mock</span><span class="o">,</span> <span class="nn">pytest</span><span class="o">,</span> <span class="nn">zmq</span>
 
     <span class="sd">&quot;&quot;&quot;Tests :class:`pongproc.PongProc`.&quot;&quot;&quot;</span>
 
     <span class="k">def</span> <span class="nf">test_setup</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">pp</span><span class="p">):</span>
-        <span class="n">pp</span><span class="o">.</span><span class="n">stream</span> <span class="o">=</span> <span class="n">mock</span><span class="o">.</span><span class="n">Mock</span><span class="p">(</span><span class="n">side_effect</span><span class="o">=</span><span class="k">lambda</span> <span class="o">*</span><span class="n">a</span><span class="p">,</span> <span class="o">**</span><span class="n">k</span><span class="p">:</span> <span class="p">(</span><span class="n">a</span><span class="p">[</span><span class="mi">0</span><span class="p">],</span> <span class="n">mock</span><span class="o">.</span><span class="n">Mock</span><span class="p">()))</span>
+        <span class="k">def</span> <span class="nf">make_stream</span><span class="p">(</span><span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span>
+            <span class="n">stream</span> <span class="o">=</span> <span class="n">mock</span><span class="o">.</span><span class="n">Mock</span><span class="p">()</span>
+            <span class="n">stream</span><span class="o">.</span><span class="n">type</span> <span class="o">=</span> <span class="n">args</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span>
+            <span class="k">return</span> <span class="n">stream</span><span class="p">,</span> <span class="n">mock</span><span class="o">.</span><span class="n">Mock</span><span class="p">()</span>
+        <span class="n">pp</span><span class="o">.</span><span class="n">stream</span> <span class="o">=</span> <span class="n">mock</span><span class="o">.</span><span class="n">Mock</span><span class="p">(</span><span class="n">side_effect</span><span class="o">=</span><span class="n">make_stream</span><span class="p">)</span>
 
-        <span class="k">with</span> <span class="n">mock</span><span class="o">.</span><span class="n">patch</span><span class="p">(</span><span class="s">&#39;zmqproc.ZmqProcess.setup&#39;</span><span class="p">)</span> <span class="k">as</span> <span class="n">setup_mock</span><span class="p">:</span>
+        <span class="k">with</span> <span class="n">mock</span><span class="o">.</span><span class="n">patch</span><span class="p">(</span><span class="s">&#39;base.ZmqProcess.setup&#39;</span><span class="p">)</span> <span class="k">as</span> <span class="n">setup_mock</span><span class="p">:</span>
             <span class="n">pp</span><span class="o">.</span><span class="n">setup</span><span class="p">()</span>
             <span class="k">assert</span> <span class="n">setup_mock</span><span class="o">.</span><span class="n">call_count</span> <span class="o">==</span> <span class="mi">1</span>
 
-        <span class="c"># Assert that all streams were created</span>
         <span class="k">assert</span> <span class="n">pp</span><span class="o">.</span><span class="n">stream</span><span class="o">.</span><span class="n">call_args_list</span> <span class="o">==</span> <span class="p">[</span>
-            <span class="p">((</span><span class="n">zmq</span><span class="o">.</span><span class="n">REP</span><span class="p">,</span> <span class="p">(</span><span class="n">host</span><span class="p">,</span> <span class="n">port</span><span class="p">)),</span>
-                <span class="nb">dict</span><span class="p">(</span><span class="n">bind</span><span class="o">=</span><span class="bp">True</span><span class="p">,</span> <span class="n">callback</span><span class="o">=</span><span class="n">pp</span><span class="o">.</span><span class="n">handle_rep_stream</span><span class="p">)),</span>
+            <span class="p">((</span><span class="n">zmq</span><span class="o">.</span><span class="n">REP</span><span class="p">,</span> <span class="p">(</span><span class="n">host</span><span class="p">,</span> <span class="n">port</span><span class="p">)),</span> <span class="nb">dict</span><span class="p">(</span><span class="n">bind</span><span class="o">=</span><span class="bp">True</span><span class="p">)),</span>
         <span class="p">]</span>
-        <span class="k">assert</span> <span class="n">pp</span><span class="o">.</span><span class="n">rep_stream</span> <span class="o">==</span> <span class="n">zmq</span><span class="o">.</span><span class="n">REP</span>
+        <span class="k">assert</span> <span class="n">pp</span><span class="o">.</span><span class="n">rep_stream</span><span class="o">.</span><span class="n">type</span> <span class="o">==</span> <span class="n">zmq</span><span class="o">.</span><span class="n">REP</span>
+
+        <span class="c"># Test if the message handler was configured correctly</span>
+        <span class="n">rsh</span> <span class="o">=</span> <span class="n">pp</span><span class="o">.</span><span class="n">rep_stream</span><span class="o">.</span><span class="n">on_recv</span><span class="o">.</span><span class="n">call_args</span><span class="p">[</span><span class="mi">0</span><span class="p">][</span><span class="mi">0</span><span class="p">]</span>  <span class="c"># Get the msg handler</span>
+        <span class="k">assert</span> <span class="n">rsh</span><span class="o">.</span><span class="n">_rep_stream</span> <span class="o">==</span> <span class="n">pp</span><span class="o">.</span><span class="n">rep_stream</span>
+        <span class="k">assert</span> <span class="n">rsh</span><span class="o">.</span><span class="n">_stop</span> <span class="o">==</span> <span class="n">pp</span><span class="o">.</span><span class="n">stop</span>
 
     <span class="k">def</span> <span class="nf">test_run</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">pp</span><span class="p">):</span>
         <span class="n">pp</span><span class="o">.</span><span class="n">setup</span> <span class="o">=</span> <span class="n">mock</span><span class="o">.</span><span class="n">Mock</span><span class="p">()</span>
         <span class="n">pp</span><span class="o">.</span><span class="n">stop</span><span class="p">()</span>
         <span class="k">assert</span> <span class="n">pp</span><span class="o">.</span><span class="n">loop</span><span class="o">.</span><span class="n">stop</span><span class="o">.</span><span class="n">call_count</span> <span class="o">==</span> <span class="mi">1</span>
 </pre></div>
-<p>The callbacks for streams (e.g., <em>PongProc.handle_rep_stream</em> in our case) can
-get a bit more complicated, so I’ve split the test up in one test per message
-type plus one extra test that checks if invalid messages are handled correctly.
-If all your callbacks behave the same in that case (e.g., they all raise an
-error or just print something), you can handle them with one test case and the
-<em>parametrize</em> decorator:</p>
-<div class="highlight"><pre><span class="c"># test/test_pongproc.py</span>
+</div>
+<div class="section" id="repstreamhandler">
+<h2>RepStreamHandler</h2>
+<p>Testing the actual message handler requires some mocks, but is apart from that
+straight forward. A <em>funcarg</em> method creates an instance of the message handler
+for each test case which we feed with a message. We than check if the
+application logic was called correctly and/or if a correct reply is sent:</p>
+<div class="highlight"><pre><span class="c"># example_app/test/test_pongproc.py</span>
 
-    <span class="nd">@pytest.mark.parametrize</span><span class="p">((</span><span class="s">&#39;handler&#39;</span><span class="p">,</span> <span class="s">&#39;msg&#39;</span><span class="p">),</span> <span class="p">[</span>
-        <span class="p">(</span><span class="s">&#39;handle_rep_stream&#39;</span><span class="p">,</span> <span class="p">[</span><span class="s">&#39;[&quot;spam&quot;, []]&#39;</span><span class="p">]),</span>
-        <span class="c"># You can add more handlers here</span>
-    <span class="p">])</span>
-    <span class="k">def</span> <span class="nf">test_handle_bad_msg</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">pp</span><span class="p">,</span> <span class="n">handler</span><span class="p">,</span> <span class="n">msg</span><span class="p">):</span>
-        <span class="n">pytest</span><span class="o">.</span><span class="n">raises</span><span class="p">(</span><span class="ne">RuntimeError</span><span class="p">,</span> <span class="nb">getattr</span><span class="p">(</span><span class="n">pp</span><span class="p">,</span> <span class="n">handler</span><span class="p">),</span> <span class="n">msg</span><span class="p">)</span>
-</pre></div>
-<p>Testing if <em>stop</em> and <em>ping</em> messages are handled correctly is now
-straightforward. We perform some mocking (for the application logic and the
-stream that sends the reply), pass our message to the handler and then just
-check if it did the right things right:</p>
-<div class="highlight"><pre><span class="c"># test/test_pongproc.py</span>
+<span class="k">def</span> <span class="nf">pytest_funcarg__rsh</span><span class="p">(</span><span class="n">request</span><span class="p">):</span>
+    <span class="sd">&quot;&quot;&quot;Creates a RepStreamHandler instance.&quot;&quot;&quot;</span>
+    <span class="k">return</span> <span class="n">pongproc</span><span class="o">.</span><span class="n">RepStreamHandler</span><span class="p">(</span>
+            <span class="n">rep_stream</span><span class="o">=</span><span class="n">mock</span><span class="o">.</span><span class="n">Mock</span><span class="p">(),</span>
+            <span class="n">stop</span><span class="o">=</span><span class="n">mock</span><span class="o">.</span><span class="n">Mock</span><span class="p">(),</span>
+            <span class="n">ping_handler</span><span class="o">=</span><span class="n">mock</span><span class="o">.</span><span class="n">Mock</span><span class="p">(</span><span class="n">spec_set</span><span class="o">=</span><span class="n">pongproc</span><span class="o">.</span><span class="n">PingHandler</span><span class="p">()))</span>
 
-    <span class="k">def</span> <span class="nf">test_stop_msg</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">pp</span><span class="p">):</span>
-        <span class="n">pp</span><span class="o">.</span><span class="n">stop</span> <span class="o">=</span> <span class="n">mock</span><span class="o">.</span><span class="n">Mock</span><span class="p">()</span>
-        <span class="n">pp</span><span class="o">.</span><span class="n">handle_rep_stream</span><span class="p">([</span><span class="n">b</span><span class="s">&#39;[&quot;plzdiekthxbye&quot;, null]&#39;</span><span class="p">])</span>
-        <span class="k">assert</span> <span class="n">pp</span><span class="o">.</span><span class="n">stop</span><span class="o">.</span><span class="n">call_count</span> <span class="o">==</span> <span class="mi">1</span>
 
-    <span class="k">def</span> <span class="nf">test_ping</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">pp</span><span class="p">):</span>
-        <span class="n">msg</span> <span class="o">=</span> <span class="p">[</span><span class="s">&#39;ping&#39;</span><span class="p">,</span> <span class="mi">1</span><span class="p">]</span>  <span class="c"># Input message</span>
-        <span class="n">retval</span> <span class="o">=</span> <span class="s">&#39;spam&#39;</span>  <span class="c"># Return value for PingHandler.make_pong</span>
-        <span class="n">pp</span><span class="o">.</span><span class="n">ping_handler</span> <span class="o">=</span> <span class="n">mock</span><span class="o">.</span><span class="n">Mock</span><span class="p">(</span><span class="n">spec_set</span><span class="o">=</span><span class="n">pongproc</span><span class="o">.</span><span class="n">PingHandler</span><span class="p">)</span>
-        <span class="n">pp</span><span class="o">.</span><span class="n">ping_handler</span><span class="o">.</span><span class="n">make_pong</span><span class="o">.</span><span class="n">return_value</span> <span class="o">=</span> <span class="n">retval</span>
-        <span class="n">pp</span><span class="o">.</span><span class="n">rep_stream</span> <span class="o">=</span> <span class="n">mock</span><span class="o">.</span><span class="n">Mock</span><span class="p">()</span>
+<span class="k">class</span> <span class="nc">TestRepStreamHandler</span><span class="p">(</span><span class="nb">object</span><span class="p">):</span>
+    <span class="k">def</span> <span class="nf">test_ping</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">rsh</span><span class="p">):</span>
+        <span class="n">msg</span> <span class="o">=</span> <span class="p">[</span><span class="s">&#39;ping&#39;</span><span class="p">,</span> <span class="mi">1</span><span class="p">]</span>
+        <span class="n">retval</span> <span class="o">=</span> <span class="s">&#39;spam&#39;</span>
+        <span class="n">rsh</span><span class="o">.</span><span class="n">_ping_handler</span> <span class="o">=</span> <span class="n">mock</span><span class="o">.</span><span class="n">Mock</span><span class="p">(</span><span class="n">spec_set</span><span class="o">=</span><span class="n">pongproc</span><span class="o">.</span><span class="n">PingHandler</span><span class="p">)</span>
+        <span class="n">rsh</span><span class="o">.</span><span class="n">_ping_handler</span><span class="o">.</span><span class="n">make_pong</span><span class="o">.</span><span class="n">return_value</span> <span class="o">=</span> <span class="n">retval</span>
 
-        <span class="n">pp</span><span class="o">.</span><span class="n">handle_rep_stream</span><span class="p">([</span><span class="n">json</span><span class="o">.</span><span class="n">dumps</span><span class="p">(</span><span class="n">msg</span><span class="p">)])</span>
+        <span class="n">rsh</span><span class="p">([</span><span class="n">json</span><span class="o">.</span><span class="n">dumps</span><span class="p">(</span><span class="n">msg</span><span class="p">)])</span>
 
-        <span class="k">assert</span> <span class="n">pp</span><span class="o">.</span><span class="n">ping_handler</span><span class="o">.</span><span class="n">make_pong</span><span class="o">.</span><span class="n">call_args</span> <span class="o">==</span> <span class="p">((</span><span class="n">msg</span><span class="p">[</span><span class="mi">1</span><span class="p">],),</span> <span class="p">{})</span>
-        <span class="k">assert</span> <span class="n">pp</span><span class="o">.</span><span class="n">rep_stream</span><span class="o">.</span><span class="n">send_json</span><span class="o">.</span><span class="n">call_args</span> <span class="o">==</span> <span class="p">((</span><span class="n">retval</span><span class="p">,),</span> <span class="p">{})</span>
+        <span class="k">assert</span> <span class="n">rsh</span><span class="o">.</span><span class="n">_ping_handler</span><span class="o">.</span><span class="n">make_pong</span><span class="o">.</span><span class="n">call_args</span> <span class="o">==</span> <span class="p">((</span><span class="n">msg</span><span class="p">[</span><span class="mi">1</span><span class="p">],),</span> <span class="p">{})</span>
+        <span class="k">assert</span> <span class="n">rsh</span><span class="o">.</span><span class="n">_rep_stream</span><span class="o">.</span><span class="n">send_json</span><span class="o">.</span><span class="n">call_args</span> <span class="o">==</span> <span class="p">((</span><span class="n">retval</span><span class="p">,),</span> <span class="p">{})</span>
+
+    <span class="k">def</span> <span class="nf">test_plzdiekthybye</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">rsh</span><span class="p">):</span>
+        <span class="n">rsh</span><span class="p">([</span><span class="n">b</span><span class="s">&#39;[&quot;plzdiekthxbye&quot;, null]&#39;</span><span class="p">])</span>
+        <span class="k">assert</span> <span class="n">rsh</span><span class="o">.</span><span class="n">_stop</span><span class="o">.</span><span class="n">call_count</span> <span class="o">==</span> <span class="mi">1</span>
 </pre></div>
 </div>
 <div class="section" id="pinghandler">
 <h2>PingHandler</h2>
 <p>When we are done with all that network stuff, we can finally test the
 application logic. Easy-peasy in our case:</p>
-<div class="highlight"><pre><span class="c"># test/test_pongproc.py</span>
+<div class="highlight"><pre><span class="c"># example_app/test/test_pongproc.py</span>
 
 <span class="k">def</span> <span class="nf">pytest_funcarg__ph</span><span class="p">(</span><span class="n">request</span><span class="p">):</span>
     <span class="sd">&quot;&quot;&quot;Creates a PingHandler instance.&quot;&quot;&quot;</span>

File article_2.txt

View file
     platform darwin -- Python 3.2.2 -- pytest-2.2.3
     collected 11 items
 
-    test/test_pongproc.py .......
-    test/test_zmqproc.py ....
+    example_app/test/test_base.py ....
+    example_app/test/test_pongproc.py .......
 
     ================ 11 passed in 0.12 seconds =================
 
 
 .. code-block:: python
 
-    # test/test_zmqproc.py
+    # example_app/test/test_zmqproc.py
     from zmq.eventloop import ioloop
     import mock
     import pytest
 
 
     class TestZmqProcess(object):
-        """Tests for :class:`zmqproc.ZmqProcess`."""
+        """Tests for :class:`base.ZmqProcess`."""
 
         def test_setup(self):
-            zp = zmqproc.ZmqProcess()
+            zp = base.ZmqProcess()
             zp.setup()
 
             assert isinstance(zp.context, zmq.Context)
 
 .. code-block:: python
 
-    # test/test_zmqproc.py
+    # example_app/test/test_zmqproc.py
 
         @pytest.mark.parametrize('kwargs', [
             dict(sock_type=23, addr='127.0.0.1:1234', bind=True,
 
 .. code-block:: python
 
-    # test/test_zmqproc.py
+    # example_app/test/test_zmqproc.py
 
-            zp = zmqproc.ZmqProcess()
+            zp = base.ZmqProcess()
 
             # Patch the ZmqProcess instance
             zp.context = mock.Mock(spec_set=zmq.Context)
 
 .. code-block:: python
 
-    # test/test_zmqproc.py
+    # example_app/test/test_zmqproc.py
 
             # Patch ZMQStream and start testing
             with mock.patch('zmq.eventloop.zmqstream.ZMQStream') as zmqstream_mock:
 
 .. code-block:: python
 
-    # test/test_zmqproc.py
+    # example_app/test/test_zmqproc.py
 
                 # Assert that the return values are correct
                 assert stream is zmqstream_mock.return_value
 not. This makes it easier for me to find all assertions in a test.*
 
 
+MessageHandler
+^^^^^^^^^^^^^^
+
+The *MessageHandler* base class has only one methd, *__call__*, but I split the
+test for it into two methods—one that tests the JSON-loading functionality and
+one that checks if the correct handler method is called:
+
+.. code-block:: python
+
+    # example_app/test/test_base.py
+
+    class TestMessageHandler(object):
+        """Tests for :class:`base.TestMessageHandler`."""
+
+        @pytest.mark.parametrize(('idx', 'msg'), [
+            (-1, [23, b'["test", null]']),
+            (1, [23, b'["test", "spam"]', 42]),
+            (TypeError, [23, 42]),
+            (ValueError, [23, b'["test"]23spam']),
+        ])
+        def test_call_json_load(self, idx, msg):
+            handler = mock.Mock()
+            mh = base.MessageHandler(idx if isinstance(idx, int) else -1)
+            mh.test = handler
+
+            if isinstance(idx, int):
+                mh(msg)
+                assert handler.call_count == 1
+            else:
+                pytest.raises(idx, mh, msg)
+
+        @pytest.mark.parametrize(('ok', 'msg'), [
+            (True, [23, b'["test", "spam"]', 42]),
+            (AttributeError, [23, b'["_test", "spam"]', 42]),
+            (TypeError, [23, b'["spam", "spam"]', 42]),
+            (AttributeError, [23, b'["eggs", "spam"]', 42]),
+        ])
+        def test_call_get_handler(self, ok, msg):
+            handler = mock.Mock()
+            mh = base.MessageHandler(1)
+            mh.test = handler
+            mh.spam = 'spam'
+
+            if ok is True:
+                mh(msg)
+                assert handler.call_args == (
+                        (msg[0], 'spam', msg[2]), {})
+            else:
+                pytest.raises(ok, mh, msg)
+
+
 PongProc
 ^^^^^^^^
 
 
 .. code-block:: python
 
-    # test/test_pongproc.py
+    # example_app/test/test_pongproc.py
     from zmq.utils import jsonapi as json
     import mock, pytest, zmq
 
         """Tests :class:`pongproc.PongProc`."""
 
         def test_setup(self, pp):
-            pp.stream = mock.Mock(side_effect=lambda *a, **k: (a[0], mock.Mock()))
+            def make_stream(*args, **kwargs):
+                stream = mock.Mock()
+                stream.type = args[0]
+                return stream, mock.Mock()
+            pp.stream = mock.Mock(side_effect=make_stream)
 
-            with mock.patch('zmqproc.ZmqProcess.setup') as setup_mock:
+            with mock.patch('base.ZmqProcess.setup') as setup_mock:
                 pp.setup()
                 assert setup_mock.call_count == 1
 
-            # Assert that all streams were created
             assert pp.stream.call_args_list == [
-                ((zmq.REP, (host, port)),
-                    dict(bind=True, callback=pp.handle_rep_stream)),
+                ((zmq.REP, (host, port)), dict(bind=True)),
             ]
-            assert pp.rep_stream == zmq.REP
+            assert pp.rep_stream.type == zmq.REP
+
+            # Test if the message handler was configured correctly
+            rsh = pp.rep_stream.on_recv.call_args[0][0]  # Get the msg handler
+            assert rsh._rep_stream == pp.rep_stream
+            assert rsh._stop == pp.stop
 
         def test_run(self, pp):
             pp.setup = mock.Mock()
             assert pp.loop.stop.call_count == 1
 
 
-The callbacks for streams (e.g., *PongProc.handle_rep_stream* in our case) can
-get a bit more complicated, so I’ve split the test up in one test per message
-type plus one extra test that checks if invalid messages are handled correctly.
-If all your callbacks behave the same in that case (e.g., they all raise an
-error or just print something), you can handle them with one test case and the
-*parametrize* decorator:
+RepStreamHandler
+^^^^^^^^^^^^^^^^
+
+Testing the actual message handler requires some mocks, but is apart from that
+straight forward. A *funcarg* method creates an instance of the message handler
+for each test case which we feed with a message. We than check if the
+application logic was called correctly and/or if a correct reply is sent:
 
 .. code-block:: python
 
-    # test/test_pongproc.py
+    # example_app/test/test_pongproc.py
 
-        @pytest.mark.parametrize(('handler', 'msg'), [
-            ('handle_rep_stream', ['["spam", []]']),
-            # You can add more handlers here
-        ])
-        def test_handle_bad_msg(self, pp, handler, msg):
-            pytest.raises(RuntimeError, getattr(pp, handler), msg)
+    def pytest_funcarg__rsh(request):
+        """Creates a RepStreamHandler instance."""
+        return pongproc.RepStreamHandler(
+                rep_stream=mock.Mock(),
+                stop=mock.Mock(),
+                ping_handler=mock.Mock(spec_set=pongproc.PingHandler()))
 
 
-Testing if *stop* and *ping* messages are handled correctly is now
-straightforward. We perform some mocking (for the application logic and the
-stream that sends the reply), pass our message to the handler and then just
-check if it did the right things right:
+    class TestRepStreamHandler(object):
+        def test_ping(self, rsh):
+            msg = ['ping', 1]
+            retval = 'spam'
+            rsh._ping_handler = mock.Mock(spec_set=pongproc.PingHandler)
+            rsh._ping_handler.make_pong.return_value = retval
 
-.. code-block:: python
+            rsh([json.dumps(msg)])
 
-    # test/test_pongproc.py
+            assert rsh._ping_handler.make_pong.call_args == ((msg[1],), {})
+            assert rsh._rep_stream.send_json.call_args == ((retval,), {})
 
-        def test_stop_msg(self, pp):
-            pp.stop = mock.Mock()
-            pp.handle_rep_stream([b'["plzdiekthxbye", null]'])
-            assert pp.stop.call_count == 1
-
-        def test_ping(self, pp):
-            msg = ['ping', 1]  # Input message
-            retval = 'spam'  # Return value for PingHandler.make_pong
-            pp.ping_handler = mock.Mock(spec_set=pongproc.PingHandler)
-            pp.ping_handler.make_pong.return_value = retval
-            pp.rep_stream = mock.Mock()
-
-            pp.handle_rep_stream([json.dumps(msg)])
-
-            assert pp.ping_handler.make_pong.call_args == ((msg[1],), {})
-            assert pp.rep_stream.send_json.call_args == ((retval,), {})
+        def test_plzdiekthybye(self, rsh):
+            rsh([b'["plzdiekthxbye", null]'])
+            assert rsh._stop.call_count == 1
 
 
 PingHandler
 
 .. code-block:: python
 
-    # test/test_pongproc.py
+    # example_app/test/test_pongproc.py
 
     def pytest_funcarg__ph(request):
         """Creates a PingHandler instance."""