<?xml version="1.0" encoding="utf-8"?>
<?xml-stylesheet href="/blog/feed.xsl" type="text/xsl"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>Dimiter Petrov</title>
  <subtitle>A programming blog written by Dimiter Petrov</subtitle>
  <link href="https://dimiterpetrov.com/blog/feed.xml" rel="self"/>
  <link href="https://dimiterpetrov.com/blog/" rel="alternate" />
  <updated>2024-11-06T00:00:00Z</updated>
  <id>https://dimiterpetrov.com/blog/</id>
  <author>
    <name>Dimiter Petrov</name>
  </author>
  
    
  
  <entry>
    <title>&quot;We don&#39;t deploy on Fridays&quot;</title>
    <link href="https://dimiterpetrov.com/blog/we-dont-deploy-on-fridays/"/>
    <updated>2024-11-06T00:00:00Z</updated>
    <id>https://dimiterpetrov.com/blog/we-dont-deploy-on-fridays/</id>
    
    <content type="html">&lt;p&gt;Hearing &amp;quot;we don&#39;t deploy on Fridays&amp;quot; makes me sad, because I always encounter it as a precautionary measure.&lt;/p&gt;
&lt;h2&gt;Fear and precaution&lt;/h2&gt;
&lt;p&gt;The subtext is &amp;quot;it&#39;s not safe to deploy&amp;quot; and &amp;quot;if there is a problem, it takes too long to fix&amp;quot;.&lt;/p&gt;
&lt;p&gt;There is plenty out there about this topic. Search for &amp;quot;deployment on Friday&amp;quot;. I almost forgot &lt;a href=&quot;https://dimiterpetrov.com/blog/error-prevention-vs-error-recovery&quot;&gt;I&#39;ve also written about this before&lt;/a&gt;. The arguments are more or less the same. If you are afraid to deploy, it may be because:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;you have insufficient monitoring and alerting; or&lt;/li&gt;
&lt;li&gt;the deployment process is error-prone; or&lt;/li&gt;
&lt;li&gt;deployments, and thus rollbacks, are too slow; or&lt;/li&gt;
&lt;li&gt;if something breaks, you don&#39;t know how to fix it; or&lt;/li&gt;
&lt;li&gt;risky changes are deployed all at once (vs using feature flags, gradual roll-outs, etc.).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Those are all valid reasons. But they&#39;re also valid reasons on a Monday morning. So, do address them.&lt;/p&gt;
&lt;h2&gt;Valid reasons to not deploy on Fridays&lt;/h2&gt;
&lt;p&gt;Still, I&#39;d love to hear no-deployment Fridays associated with joy rather than fear.&lt;/p&gt;
&lt;p&gt;On a literal level not everyone works on Friday. &lt;a href=&quot;https://en.wikipedia.org/wiki/Workweek_and_weekend&quot;&gt;A Monday-to-Friday workweek is not universal&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;That got me thinking: what if the work culture made it so that there is &lt;em&gt;nothing to deploy&lt;/em&gt; on Fridays?&lt;/p&gt;
&lt;p&gt;We don&#39;t deploy on Fridays, because we work a 4-day week and take Friday off (but we do deploy on Thursdays!).&lt;/p&gt;
&lt;p&gt;We don&#39;t deploy on Fridays, because we contribute to open source.&lt;/p&gt;
&lt;p&gt;We don&#39;t deploy on Fridays, because we conduct interviews.&lt;/p&gt;
&lt;p&gt;We don&#39;t deploy on Fridays, because we write blog posts.&lt;/p&gt;
&lt;p&gt;We don&#39;t deploy on Fridays, because we check and update documentation.&lt;/p&gt;
&lt;p&gt;We don&#39;t deploy on Fridays, because we work on making our test suite faster.&lt;/p&gt;
&lt;p&gt;We don&#39;t deploy on Fridays, because the developers all do customer support to get a better understanding about how the product is used.&lt;/p&gt;
&lt;p&gt;We don&#39;t deploy on Fridays, because Fridays are a dedicated learning day.&lt;/p&gt;
</content>
    
  </entry>
  
    
  
  <entry>
    <title>How I test Stimulus controllers</title>
    <link href="https://dimiterpetrov.com/blog/how-i-test-stimulus-controllers/"/>
    <updated>2024-07-02T00:00:00Z</updated>
    <id>https://dimiterpetrov.com/blog/how-i-test-stimulus-controllers/</id>
    
    <content type="html">&lt;p&gt;I caught myself relying too much on manual tests for a feature I was building on top of a &lt;a href=&quot;https://stimulus.hotwired.dev/&quot;&gt;Stimulus&lt;/a&gt; controller. Manual tests were enough for the initial proof of concept phase. However as I was adding more features to the prototype, I was also causing regressions.&lt;/p&gt;
&lt;p&gt;As I threw away the prototype and started over with a test-driven approach, I thought about how I test in general and how that applies to Stimulus.js&lt;/p&gt;
&lt;h2&gt;No jsdom-based tests with sample HTML&lt;/h2&gt;
&lt;p&gt;I&#39;ll start by stating how I &lt;em&gt;do not&lt;/em&gt; test Stimulus controllers.&lt;/p&gt;
&lt;p&gt;It would be possible to attach the controller to some fixture HTML and test things on top of &lt;a href=&quot;https://github.com/jsdom/jsdom&quot;&gt;jsdom&lt;/a&gt;. That approach would be reasonable for a library. But I&#39;m not packaging a stimulus controller. I&#39;m using it directly in my application.&lt;/p&gt;
&lt;p&gt;In applications, the mistakes I commonly make with Stimulus controllers are all in the integration with the page. Maybe I forgot to set a target, or I have a typo in a value, or it&#39;s not listening to the right event.&lt;/p&gt;
&lt;p&gt;Because these mistakes can happen at every use of a controller, I could get it right in the test code, but wrong in the production.&lt;/p&gt;
&lt;h2&gt;1-2 integration tests with Capybara (Rails system test)&lt;/h2&gt;
&lt;p&gt;What I do instead is test the functionality from a user&#39;s perspective in a Rails system test. The controller is an implementation detail that is tested implicitly.&lt;/p&gt;
&lt;h2&gt;Unit tests&lt;/h2&gt;
&lt;p&gt;Think about a Rails controller. With a handful of code paths, testing the controller is not much of a problem. But the more code paths and edge cases there are, the harder it becomes to prepare the initial test state for each scenario. Not to mention that all these tests exercise the request/response stack and are slow in aggregate.&lt;/p&gt;
&lt;p&gt;Usually, with a Rails controller, I extract a domain object and put the business logic there. The controller is left with HTTP concerns: parsing parameters and headers for the request, setting headers and rendering the response.&lt;/p&gt;
&lt;p&gt;A few tests would cover the integration between the domain object and the controller. The majority of tests test the domain object in isolation. Given these inputs, it has those outputs and/or side-effects.&lt;/p&gt;
&lt;p&gt;I see a Stimulus controller the same way. It excels at being the glue between the HTML document and the business logic. It allows easily subscribing to events, reading properties and setting properties in the DOM. The core business logic doesn&#39;t need to directly depend on the DOM. It can be extracted and tested in isolation.&lt;/p&gt;
&lt;h2&gt;An example with a music player&lt;/h2&gt;
&lt;p&gt;Consider a music player on a web page. Browsers have a default user interface for audio and video elements, but they vary between browsers. We want the player to follow the brand guidelines. Also, the player can play playlists (say that five times faster). There is no web standard for that, so we have to implement it ourselves.&lt;/p&gt;
&lt;p&gt;I like two things about this example. First, it&#39;s extracted (and slightly simplified) from a real application. Second, developers tend to not test things when it looks too hard and testing audio playback seemed too hard to do automatically to me at first.&lt;/p&gt;
&lt;p&gt;How do I break down this feature and test it?&lt;/p&gt;
&lt;h3&gt;The HTML&lt;/h3&gt;
&lt;p&gt;The basis is a &lt;code&gt;&amp;lt;video&amp;gt;&lt;/code&gt; element. That can play both audio-only and video files.
The native player controls are hidden, but there are custom controls for play/pause as well as going to the previous and next track in a playlist.&lt;/p&gt;
&lt;p&gt;Here are the relevant bits of the code:&lt;/p&gt;
&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;data-controller&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;player&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;video&lt;/span&gt;
    &lt;span class=&quot;token attr-name&quot;&gt;data-player-target&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;player&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token attr-name&quot;&gt;data-action&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;
      playing-&gt;player#playing
      pause-&gt;player#paused
      ended-&gt;player#ended
      playable:play@window-&gt;player#play
    &lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token attr-name&quot;&gt;data-testid&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;player&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;video&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;

  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;button&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;data-action&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;player#previous&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
      Previous
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;button&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;

    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;button&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;data-player-target&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;playButton&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;data-action&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;player#resume&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
      Play
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;button&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;

    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;button&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;data-player-target&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;pauseButton&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;data-action&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;player#pause&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;hidden&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
      Pause
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;button&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;

    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;button&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;data-action&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;player#next&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
      Next
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;button&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Notice the video element is a Stimulus target. It also has event listeners attached. These allow us to keep the player controls in sync with the player state.&lt;/p&gt;
&lt;h3&gt;Separation of concerns with cross-controller communication&lt;/h3&gt;
&lt;p&gt;Playback starts when pressing the play button of a song or a playlist on the page. There&#39;s a separate controller that dispatches a custom event. The following Stimulus action is what listens to play request events and reacts by actually starting the payback:&lt;/p&gt;
&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;data-action=&quot;playable:play@window-&gt;player#play&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This follows the pattern for &lt;a href=&quot;https://stimulus.hotwired.dev/reference/controllers#cross-controller-coordination-with-events&quot;&gt;Stimulus.js cross-controller communication with events&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;In order to keep the example focused, I won&#39;t show this controller&#39;s code. The cross-controller communication is covered by the system test that I show at the end.&lt;/p&gt;
&lt;h3&gt;The player controller&lt;/h3&gt;
&lt;p&gt;The player controller looks roughly like this:&lt;/p&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; Controller &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;@hotwired/stimulus&quot;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; PlayableList &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;../player/playable_list&quot;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;extends&lt;/span&gt; Controller &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt; targets &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;playButton&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;pauseButton&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;player&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;static&lt;/span&gt; classes &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;hidden&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;

  &lt;span class=&quot;token function&quot;&gt;connect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;playableList &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;PlayableList&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;/*
   * API
   */&lt;/span&gt;

  &lt;span class=&quot;token function&quot;&gt;play&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;playableList&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;playNow&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;detail&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;switchTo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;playableList&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;currentTrack&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token function&quot;&gt;resume&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token function&quot;&gt;pause&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token function&quot;&gt;next&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; old &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;playableList&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;currentTrack
    &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;playableList&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;skipForward&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;old &lt;span class=&quot;token operator&quot;&gt;!==&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;playableList&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;currentTrack&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;switchTo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;playableList&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;currentTrack&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token function&quot;&gt;previous&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token function&quot;&gt;switchTo&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;track&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;playSource&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;track&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;source&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token function&quot;&gt;playSource&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;source&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;playerTarget&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;src &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; source
    &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;playerTarget&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;play&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;catch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;handleError&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;error&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token function&quot;&gt;handleError&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;playerTarget&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;pause&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;/*
   * Events
   *
   * Named after HTMLMediaElement events
   * https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement#events
   */&lt;/span&gt;

  &lt;span class=&quot;token function&quot;&gt;playing&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;hidePlayButton&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;showPauseButton&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token function&quot;&gt;paused&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;showPlayButton&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;hidePauseButton&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token function&quot;&gt;ended&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;showPlayButton&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;hidePauseButton&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;next&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;// https://developer.mozilla.org/en-US/docs/Web/API/MediaError&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token function&quot;&gt;handleError&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;error&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;/*
   * User interface
   */&lt;/span&gt;

  &lt;span class=&quot;token function&quot;&gt;hidePlayButton&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token function&quot;&gt;showPlayButton&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token function&quot;&gt;hidePauseButton&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token function&quot;&gt;showPauseButton&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;The core business logic and its tests&lt;/h3&gt;
&lt;p&gt;The player controller can hold player state, but it doesn&#39;t have to know the implementation details. Those details are in what I call here &lt;code&gt;PlayableList&lt;/code&gt;, which controls moving back and forth in the list of tracks.&lt;/p&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;PlayableList&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;constructor&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; history &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; current &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; queue &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;history &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; history
    &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;current &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; current
    &lt;span class=&quot;token keyword&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;queue &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; queue
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token function&quot;&gt;skipBackward&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token function&quot;&gt;skipForward&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token function&quot;&gt;playNow&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;playable&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;get&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;currentTrack&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It&#39;s precisely thanks to that abstraction that we can now easily unit test. Look at the following test for example.&lt;/p&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; PlayableList &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;player/playable_list&quot;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; Track &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;player/track&quot;&lt;/span&gt;

&lt;span class=&quot;token function&quot;&gt;describe&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;PlayableList&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;test&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;skipping to next then previous returns to the initial item&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; track1 &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Track&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token literal-property property&quot;&gt;title&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;track1&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; track2 &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Track&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token literal-property property&quot;&gt;title&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;track2&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; list &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;PlayableList&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token literal-property property&quot;&gt;current&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; track1&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token literal-property property&quot;&gt;queue&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;track2&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

    list&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;skipForward&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    list&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;skipBackward&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;token function&quot;&gt;expect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;list&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;toEqual&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;PlayableList&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token literal-property property&quot;&gt;history&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;track1&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token literal-property property&quot;&gt;current&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; track1&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token literal-property property&quot;&gt;queue&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;track2&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The assertion is comparing two data structures (&lt;a href=&quot;https://jestjs.io/docs/expect#toequalvalue&quot;&gt;Jest&#39;s &lt;code&gt;toEqual&lt;/code&gt; matcher&lt;/a&gt; does a &amp;quot;deep&amp;quot; equality check).&lt;/p&gt;
&lt;p&gt;The JavaScript unit tests all look like that. They are fast, they are short, they are clear.&lt;/p&gt;
&lt;p&gt;I used &lt;a href=&quot;https://jestjs.io/&quot;&gt;Jest&lt;/a&gt; for the JavaScript tests, but the test framework doesn&#39;t matter. I only needed to configure two things to make these unit tests run with Jest: the location of the test files (&lt;code&gt;testMatch&lt;/code&gt;) and the location of the production code (&lt;code&gt;moduleDirectories&lt;/code&gt;). This is my &lt;code&gt;jest.config.js&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;module&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;exports &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;testMatch&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;**/test/**/*.test.js&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;cacheDirectory&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;./tmp/cache/jest&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;transformIgnorePatterns&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;node_modules&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;moduleDirectories&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;node_modules&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;app/javascript&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;There&#39;s a common argument that modifying code just for the sake of testing is an anti-pattern. I have two counter arguments specific to this example:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;When parts of the business logic are self-contained, it becomes easier to focus on the individual modules. I&#39;ve hit enough edge cases that I was glad to be able to quickly modify that part of the code in isolation and test it in isolation.&lt;/li&gt;
&lt;li&gt;An older version of this app had JavaScript controllers with a similar structure, but not powered by Stimulus.js, and the playlist management logic was intertwined in a controller. If the &lt;code&gt;PlayableList&lt;/code&gt; abstraction had existed, it would have made the transition to Stimulus faster.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;The system test&lt;/h3&gt;
&lt;p&gt;Now let&#39;s look at the system test. I&#39;ve hard-coded some strings to make the excerpt easier to read outside of the application.&lt;/p&gt;
&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;application_system_test_case&quot;&lt;/span&gt;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;PlayerTest&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; ApplicationSystemTestCase
  driven_by &lt;span class=&quot;token symbol&quot;&gt;:selenium&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;using&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;:headless_chrome&lt;/span&gt;

  test &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;playing a playlist&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;do&lt;/span&gt;
    visit &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&#39;/playlists/1&#39;&lt;/span&gt;&lt;/span&gt;
    find_button&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Play Playlist 1&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;click
    find_button&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Pause&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    player &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; find&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;[data-testid=player]&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    assert_playing&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;player&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    first_source &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; player&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;src&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
    find_button&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Next&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;click
    assert_playing&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;player&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    assert first_source &lt;span class=&quot;token operator&quot;&gt;!=&lt;/span&gt; player&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;src&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token function&quot;&gt;assert_playing&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;element&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    Timeout&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;timeout&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;Capybara&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;default_max_wait_time&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;do&lt;/span&gt;
      playing &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;
      loop &lt;span class=&quot;token keyword&quot;&gt;do&lt;/span&gt;
        playing &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; page&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;evaluate_script&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;~&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;JS&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; element&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
          &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;function&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;element&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
              element&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;duration &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt;
                 &lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;element&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;paused &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt;
                 &lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;element&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ended &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt;
                 element&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;readyState &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
          &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;arguments&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token constant&quot;&gt;JS&lt;/span&gt;
        &lt;span class=&quot;token keyword&quot;&gt;break&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; playing
      &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;
      assert playing
    &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It checks the common path of visiting a page and pressing play. It even tests pressing &amp;quot;Next&amp;quot; for good measure. If the workflow were even longer, I&#39;d still put it all in one system test. I like it when a system test/feature test tells a complete story. I also like to avoid repeating expensive setup and teardown between tests when possible.&lt;/p&gt;
&lt;p&gt;The usual advice about browser-based tests applies:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;minimize race conditions by first waiting for things to be on the page, or be in a stable state&lt;/li&gt;
&lt;li&gt;lean on Capybara&#39;s retry-aware finders&lt;/li&gt;
&lt;li&gt;implement custom auto retry logic if really necessary&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In the past, I&#39;ve resorted to using &lt;a href=&quot;https://www.cypress.io/&quot;&gt;Cypress&lt;/a&gt; for tests of complex JavaScript code. For this player system test, I tried &lt;a href=&quot;https://playwright.dev/&quot;&gt;Playwright&lt;/a&gt; directly in a Capybara test with the &lt;a href=&quot;https://playwright-ruby-client.vercel.app/docs/article/guides/rails_integration_with_null_driver&quot;&gt;Playwright Ruby driver&lt;/a&gt;. In the end, I realized the Capybara &lt;a href=&quot;https://rubydoc.info/github/teamcapybara/capybara/master#scripting&quot;&gt;&lt;code&gt;evaluate_script&lt;/code&gt;&lt;/a&gt; helper is enough to query the player state. That&#39;s lucky, because sticking to the Rails defaults reduces the long-term maintenance burden.&lt;/p&gt;
&lt;h2&gt;Defaults matter&lt;/h2&gt;
&lt;p&gt;I think part of the reason most Rails projects lack JavaScript tests is lack of examples.&lt;/p&gt;
&lt;p&gt;The Rails documentation includes a &lt;a href=&quot;https://guides.rubyonrails.org/testing.html&quot;&gt;testing guide&lt;/a&gt;. Rails generators create test files and placeholders for Ruby tests.
But there are no examples with JavaScript tests.&lt;/p&gt;
&lt;p&gt;I hope my example above inspires you to write more JavaScript tests for your application.&lt;/p&gt;
</content>
    
  </entry>
  
    
  
  <entry>
    <title>Dealing with flaky tests</title>
    <link href="https://thoughtbot.com/blog/dealing-with-flaky-tests"/>
    <updated>2024-05-27T00:00:00Z</updated>
    <id>https://thoughtbot.com/blog/dealing-with-flaky-tests</id>
    
    <content type="html">Flaky tests waste your team’s time. Have a process for quarantining them and debugging them.</content>
    
  </entry>
  
    
  
  <entry>
    <title>Service objects are poorly-named models</title>
    <link href="https://dimiterpetrov.com/blog/service-objects-are-poorly-named-models/"/>
    <updated>2023-12-11T00:00:00Z</updated>
    <id>https://dimiterpetrov.com/blog/service-objects-are-poorly-named-models/</id>
    
    <content type="html">&lt;p&gt;Look at any Rails codebase that&#39;s older than a few months. I bet you&#39;ll find a sub-directory of &lt;code&gt;app&lt;/code&gt; called &lt;code&gt;services&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;It comes with the best of intentions for separation of concerns. The developers want to keep controllers straightforward and focused on understanding a request and providing a response. They also want to keep models small, because those files have gotten too big and tedious to navigate and modify.&lt;/p&gt;
&lt;h2&gt;What are service objects anyway?&lt;/h2&gt;
&lt;p&gt;I think sometimes it&#39;s supporting code, &amp;quot;infrastructure&amp;quot; if you will. For example an HTTP client for an API. I am not going to say much about this type of code. Put that in a &lt;code&gt;lib&lt;/code&gt; directory if you must, and go on with your day.&lt;/p&gt;
&lt;p&gt;Sometimes a &amp;quot;service object&amp;quot; performs a series of steps, often with some conditional logic. Send a welcome email only if the user record was successfully created.&lt;/p&gt;
&lt;p&gt;Other times it&#39;s seemingly a single operation, but with a long implementation hidden away in private methods. You didn&#39;t want those polluting your ActiveRecord model class, so you put them here.&lt;/p&gt;
&lt;p&gt;At least that&#39;s how I&#39;ve used and seen these service objects used. I&#39;ve been writing Rails applications since 2011 and I still don&#39;t understand why we call this concept a service object. Maybe I should read the Domain Driven Design book.&lt;/p&gt;
&lt;h2&gt;Aside: suffixes for the sake of suffixes&lt;/h2&gt;
&lt;p&gt;Rails has a convention of suffixes. It&#39;s a &lt;code&gt;UsersController&lt;/code&gt;, and a &lt;code&gt;UserMailer&lt;/code&gt;, and an &lt;code&gt;ArchivalJob&lt;/code&gt;. So developers try to follow that convention and name something &lt;code&gt;UserSignupService&lt;/code&gt;. Again, because I don&#39;t understand what a service is, I keep reading the meaningless suffix everywhere. Why not call it &lt;code&gt;UserSignup&lt;/code&gt; or &lt;code&gt;Registration&lt;/code&gt;?&lt;/p&gt;
&lt;h2&gt;A representation of a business domain concept is called a model&lt;/h2&gt;
&lt;p&gt;Let&#39;s come back to my main point. I think we should stop thinking in terms of services and start thinking in terms of domain objects, otherwise known as models.&lt;/p&gt;
&lt;p&gt;I admit &amp;quot;model&amp;quot; is a generic name in itself. However it is a core concept in Rails&#39; Model View Controller architecture and it is widely understood.&lt;/p&gt;
&lt;p&gt;A technique I use is to step back from the code and think about the noun or noun groups used to describe concepts in the domain of the application. That also works for processes or operations.&lt;/p&gt;
&lt;p&gt;For example, when talking about sending notifications, the noun is &amp;quot;notification&amp;quot;. A noun group that comes to mind is &amp;quot;notification delivery&amp;quot;. Rather than a &lt;code&gt;NotificationService&lt;/code&gt; or a &lt;code&gt;NotificationManager&lt;/code&gt;, I define a &lt;code&gt;Notification&lt;/code&gt; with a &lt;code&gt;deliver&lt;/code&gt; method.&lt;/p&gt;
&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Notification&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token function&quot;&gt;initialize&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token symbol&quot;&gt;title&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;body&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;# ...&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token function&quot;&gt;deliver&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;# ...&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Depending on the domain, you can be more specific with the nouns. In case the delivery is a complex concept, I would extract a dedicated &lt;code&gt;NotificationDelivery&lt;/code&gt; class. If there is something special about the formatting of the notification, that logic could go in a &lt;code&gt;NotificationPayload&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;I avoid the names with the shape of &lt;code&gt;SomethingDoer&lt;/code&gt; or &lt;code&gt;DoesSomething&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Naming is hard, but taking the time to pick a good name is worth it. You&#39;ll be able to explain the concepts more easily. The application routes will fall into place. The controllers will make sense. Watch &lt;a href=&quot;https://www.youtube.com/watch?v=HctYHe-YjnE&quot;&gt;In Relentless Pursuit of REST&lt;/a&gt; for some inspiration.&lt;/p&gt;
&lt;h2&gt;Aside: directory structure&lt;/h2&gt;
&lt;p&gt;I&#39;ve tried adding nuanced directory structures in projects. For example having categories such as &lt;code&gt;errors&lt;/code&gt;, &lt;code&gt;forms&lt;/code&gt;, &lt;code&gt;policies&lt;/code&gt;, &lt;code&gt;presenters&lt;/code&gt;, &lt;code&gt;serializers&lt;/code&gt;, &lt;code&gt;validators&lt;/code&gt;, etc. I think some amount of organisation is good, but I&#39;ve definitely overdone it and I tend to put a lot more in the &lt;code&gt;models&lt;/code&gt; directory nowadays.&lt;/p&gt;
&lt;p&gt;With fuzzy finders, go to definition, and other tooling, the location of a file in a project doesn&#39;t really matter. In fact, I don&#39;t use the project file tree view when programming.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;app/services&lt;/code&gt; doesn&#39;t bother me from a discovery perspective. However, I think that the &lt;code&gt;models&lt;/code&gt; directory encourages you to name the domain object in a better way. Another benefit is that you avoid having to choose where to save a new file.&lt;/p&gt;
&lt;p&gt;If you do feel strongly about directory structure, I find namespaces to be a great way to keep related concepts together.&lt;/p&gt;
&lt;h2&gt;Namespaces help with organisation&lt;/h2&gt;
&lt;p&gt;Namespaces will not only lead you to better names, but also you&#39;ll start putting things that belong together close by in the file system. Here is a good example I&#39;ve seen in production code.&lt;/p&gt;
&lt;p&gt;The price of a line item depends on the product and product options. The code for calculating the price has become too large and complex, and the maintainers have extracted it from a method on the &lt;code&gt;LineItem&lt;/code&gt; model to its own class.&lt;/p&gt;
&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;LineItem&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; ApplicationRecord
  &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token function&quot;&gt;price&lt;/span&gt;&lt;/span&gt;
    LineItems&lt;span class=&quot;token double-colon punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Price&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;calculate
  &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now if you want to understand the pricing code, it&#39;s in one clearly delineated place. The implementation details are no longer mixed with other (private) methods of the &lt;code&gt;LineItem&lt;/code&gt; class.&lt;/p&gt;
&lt;p&gt;The code is also close by in the project structure.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;line_item.rb
line_items/
    price.rb
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;What&#39;s powerful in this example is that you retain the interface of the model (&lt;code&gt;line_item.price&lt;/code&gt;) while separating self-contained parts of the implementation. That makes the abstractions easier to discover. You&#39;re not wondering whether you can call a method on the model directly or you have to hunt down some class in &lt;code&gt;app/services&lt;/code&gt;. It goes &amp;quot;along the grain&amp;quot; of Rails.&lt;/p&gt;
&lt;p&gt;Continuing the example about notifications from earlier, the &lt;code&gt;Notification&lt;/code&gt; model could be the public interface, but different aspects about notifications could be in models in the &lt;code&gt;Notifications&lt;/code&gt; namespace. For example:&lt;/p&gt;
&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Notification&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token function&quot;&gt;initialize&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token symbol&quot;&gt;title&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;body&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token variable&quot;&gt;@payload&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Notifications&lt;span class=&quot;token double-colon punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Payload&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token symbol&quot;&gt;title&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; title&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;body&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; body&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token function&quot;&gt;deliver&lt;/span&gt;&lt;/span&gt;
    Notifications&lt;span class=&quot;token double-colon punctuation&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Delivery&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;@payload&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;format&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;perform
  &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To be clear, I would start with fewer objects, and only introduce the extra ones as the application complexity increases over time.&lt;/p&gt;
&lt;h2&gt;Try it out&lt;/h2&gt;
&lt;p&gt;The next time you&#39;re adding a new class, whether it&#39;s in your current codebase or a new project, consider the tips about naming and organisation above. See how it affects the code&#39;s design.&lt;/p&gt;
</content>
    
  </entry>
  
    
  
  <entry>
    <title>Have we moved past contempt culture?</title>
    <link href="https://dimiterpetrov.com/blog/have-we-moved-past-contempt-culture/"/>
    <updated>2023-12-07T00:00:00Z</updated>
    <id>https://dimiterpetrov.com/blog/have-we-moved-past-contempt-culture/</id>
    
    <content type="html">&lt;p&gt;In 2015 I read a &lt;a href=&quot;https://blog.aurynn.com/2015/12/16-contempt-culture&quot;&gt;great blog post on contempt culture&lt;/a&gt;. I had already noticed the described behaviour by that point, but I didn&#39;t have a word for it. Interestingly, the author of the post mentions starting programming in 2001. I started around that time, too. Probably 2002. It took 13 years before I stumbled upon an article pointing out bad behaviour I had both experienced and perpetuated. That article made a strong impression on me. In an effort of self-reflection, I&#39;m looking back at how I&#39;ve changed since reading it.&lt;/p&gt;
&lt;h2&gt;Being on the receiving side of contempt&lt;/h2&gt;
&lt;p&gt;First, I would like to mention two examples: one more general, and one more personal.&lt;/p&gt;
&lt;p&gt;The general one is about the recurring meme of &amp;quot;Ruby on Rails doesn&#39;t scale&amp;quot;. I&#39;ve been using Ruby since 2011 and I have seen so many iterations of this debate over the years that it&#39;s become boring to me. So much energy is spent arguing about the supposed demise of a technology. I keep myself out of this as much as possible.&lt;/p&gt;
&lt;p&gt;Once a friend said they don&#39;t understand why I&#39;m still working with Ruby. I answered something vague about work opportunities and a sense of belonging to a community. We then moved on from the topic. &lt;em&gt;Oof&lt;/em&gt;, friendship saved.&lt;/p&gt;
&lt;p&gt;The personal example is about being labeled. Around 2016 I got interested in the Elm programming language. I gave a presentation about it at work and later even built a project in Elm. Co-workers who didn&#39;t like the language were engaging me on the topic for a while. It became clear that we wouldn&#39;t use Elm for future work. There are many factors that can lead to the success or failure of a project. Can the team work well with the technology? Does the team communicate well? Does the team learn and continuously adapt its processes? For me it was much more important to be able to work together as a team, than to push for a technology that I personally liked. The programming language is just one tool.&lt;/p&gt;
&lt;p&gt;Even after we moved on, the discussions continued. At a party, someone from another company was making a joke about Elm. The person next them nodded in my direction and said &amp;quot;shush, he&#39;s really into Elm&amp;quot;. I didn&#39;t say anything, but I took a few things from the experience:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;I resented being labeled as &amp;quot;the Elm person&amp;quot;. Perhaps I came out as overly-enthusiastic about the language. I decided that from that point on, if I liked something, I would keep my thoughts to myself.&lt;/li&gt;
&lt;li&gt;I don&#39;t like my identity being tied to a category. I don&#39;t see myself as a Ruby developer, or an Elm developer. I even avoid the general label of developer.&lt;/li&gt;
&lt;li&gt;People have a preferred technology and love making fun of other technologies, especially when they have little experience with them.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The thing that twisted the knife was a conversation at an Elm conference later on. I was telling another attendee about my experience using Elm at work. They told me that I had not advocated for it enough. Why do we go to extremes? Why is it either the best language that we should spread everywhere, or the worst thing that we should mock out of existence?&lt;/p&gt;
&lt;h2&gt;Examining my own behaviour&lt;/h2&gt;
&lt;p&gt;The personal experience I described above was neither the first, nor the last. I&#39;ve been jaded and disillusioned, to the point where I don&#39;t even call out contempt. However I try to convey a nuanced position when asked directly about a topic. Here are a few examples.&lt;/p&gt;
&lt;p&gt;I once had to add a feature to a program written in PHP. I didn&#39;t know PHP, only the reputation of PHP. I chose to ignore that. I approached the project with an open mind. I studied the program and added tests to understand its behaviour better. I refactored some code once it had test coverage. Finally, I added the new feature and deployed. I never complained and even made a mental note of the language constructs in PHP that I wish Ruby had.&lt;/p&gt;
&lt;p&gt;At the end of a job interview, I was once asked what I thought of CSS. I said it&#39;s tricky to get right despite having used it for a long time, and that I have a lot of respect for people who write it well. The interviewer proceeded to say how bad it was and how they avoided it at all cost. I guess they were looking to bond over bad-mouthing something. I was a little puzzled, but also proud that I had not engaged in that.&lt;/p&gt;
&lt;p&gt;During the hype period of cryptocurrency (and later web3), colleagues tried to get me to work on projects in that domain, whether for side projects or new business ideas. I always declined politely. While I personally object to working on such projects, I don&#39;t impose my point of view to anyone. Someone pressed the issue once. I told them that the domain doesn&#39;t interest me and I prefer to focus on things that I find more exciting. That&#39;s the truth, and I think it also counteracts contempt culture. Keep to the things that interest you and stop talking about the things that don&#39;t.&lt;/p&gt;
&lt;p&gt;Another example are the countless lunchtime conversations about programming languages at work over the years. I usually stay silent during those, because I&#39;m tired by the repeated arguments. This is one point on which I could improve. I could steer the conversation towards the interesting things that the language everyone hates enables.
I could ask how we can get those benefits in the technology that this group prefers.&lt;/p&gt;
&lt;p&gt;When it comes to actively fighting contempt, I&#39;m proudest of my handling of difficult codebases. On many projects I keep reminding my colleagues to look past the current limitations of the code, not to blame previous developers, and focus on how we can make progress as a team.&lt;/p&gt;
</content>
    
  </entry>
  
    
  
  <entry>
    <title>Helvetic Ruby 2023: a personal retrospective</title>
    <link href="https://dimiterpetrov.com/blog/helvetic-ruby-2023-personal-retrospective/"/>
    <updated>2023-11-26T00:00:00Z</updated>
    <id>https://dimiterpetrov.com/blog/helvetic-ruby-2023-personal-retrospective/</id>
    
    <content type="html">&lt;p&gt;I ran the first edition of &lt;a href=&quot;https://helvetic-ruby.ch/&quot;&gt;Helvetic Ruby&lt;/a&gt; with two friends. This post reflects my own experience and my personal thoughts on it.&lt;/p&gt;
&lt;h2&gt;Preparation&lt;/h2&gt;
&lt;p&gt;We started working on the conference early. Our first meeting was in early January 2023 and the conference was on 24th November 2023.&lt;/p&gt;
&lt;p&gt;While there were periods of more intense preparation, we usually did a little bit every week. That kept stress levels low for me. I was not even stressed on the day of the conference.&lt;/p&gt;
&lt;p&gt;In a way, it felt easy, though I am probably looking back at it through rose-tinted glasses.&lt;/p&gt;
&lt;p&gt;The main reason is that we had a common vision of what we wanted to achieve. We sometimes disagreed on details, but always found a good solution after some discussion. I never felt ignored and I never wanted to say &amp;quot;I told you so&amp;quot;. When we did something, it&#39;s because we agreed on it as a team. If there was a problem with the outcome, we learned from it together. I guess this is the elusive alignment that all leaders talk about.&lt;/p&gt;
&lt;p&gt;Because we trusted each other, we could also take complementary responsibilities. We worked on the initial stages of each aspect together, e.g. the mix of topics we wanted for talks, or the sponsorship packages we offered. Once we confirmed the ideas, we executed them independently. A lot of the communication was about sharing encountered problems and sometimes asking for advice on them.&lt;/p&gt;
&lt;h2&gt;Schedule&lt;/h2&gt;
&lt;p&gt;We had planned breaks between most talks. The breaks were of variable length and the shortest ones were not long enough. 10-15 minutes barely sufficed for people to go the toilet and get a coffee. In comparison, the 30 minute breaks worked really well. I wonder how it would have been to have more groups of 2 back-to-back talks and a longer break afterwards.&lt;/p&gt;
&lt;p&gt;These short breaks also made it harder to get people to sit down for the next talk. Perhaps, we could get a little kitsch next time and use a cow bell to signal the end of the breaks.&lt;/p&gt;
&lt;p&gt;Despite that, we managed to stick to the schedule. I consider that a big success. My experience as a conference attendee is that usually everything gets delayed more and more throughout the day.&lt;/p&gt;
&lt;h2&gt;Venue&lt;/h2&gt;
&lt;p&gt;The venue felt cramped during the breaks. At the end of the day I heard a suggestion about removing the last 1-2 rows of chairs and encouraging people to sit at the balcony. (The room had a main floor and a balcony at the back).&lt;/p&gt;
&lt;p&gt;I was absorbed by other things and did not think about this at all. I wish we had thought about adjusting the layout on the spot.&lt;/p&gt;
&lt;h2&gt;Emceeing&lt;/h2&gt;
&lt;p&gt;I was the MC in the morning, Hana in the afternoon.&lt;/p&gt;
&lt;p&gt;I try to keep good posture when I&#39;m giving a talk, but emceeing didn&#39;t feel like giving a talk and posture was completely out of my head while I was on stage. I only noticed my bad posture on pictures after the event.&lt;/p&gt;
&lt;p&gt;I was comfortable on stage and people laughed at my jokes. I did feel inadequate when the room was too noisy and I couldn&#39;t get people&#39;s attention immediately. I guess this requires both practice and patience.&lt;/p&gt;
&lt;h2&gt;Talks&lt;/h2&gt;
&lt;p&gt;I think most people attended all talks. I suspect three reasons for that:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;There was a single track.&lt;/li&gt;
&lt;li&gt;People did manage to talk enough during the breaks.&lt;/li&gt;
&lt;li&gt;The program was compelling.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I found that the order of the talks and the variety of topics worked well. It paid to be intentional and know what we wanted to achieve before even starting the call for proposals.&lt;/p&gt;
&lt;p&gt;Multiple people mentioned to me that they liked the mix of topics and I am glad they validated our choices.&lt;/p&gt;
&lt;h2&gt;Next edition&lt;/h2&gt;
&lt;p&gt;It had been 10 years since I was last involved in organising a conference and this time I had much more to do. It was a great learning experience and I am glad to have started a new conference for my community. I&#39;m excited to work on the next edition of Helvetic Ruby.&lt;/p&gt;
</content>
    
  </entry>
  
    
  
  <entry>
    <title>3 ways to model an associated collection in Rails</title>
    <link href="https://thoughtbot.com/blog/3-ways-to-model-an-associated-collection-in-rails"/>
    <updated>2023-11-23T00:00:00Z</updated>
    <id>https://thoughtbot.com/blog/3-ways-to-model-an-associated-collection-in-rails</id>
    
    <content type="html">A look at the Postgres array type and good old join tables, each with their advantages and disadvantages.</content>
    
  </entry>
  
    
  
  <entry>
    <title>Turn off notifications</title>
    <link href="https://dimiterpetrov.com/blog/turn-off-notifications/"/>
    <updated>2023-07-26T00:00:00Z</updated>
    <id>https://dimiterpetrov.com/blog/turn-off-notifications/</id>
    
    <content type="html">&lt;p&gt;Do you find catching up tedious when you return to work after a holiday? I do. It took me a while to do something about it. I&#39;d even start dreading my return to work on the last day of holiday. On my first day back, I&#39;d open my email inbox and see tens, if not hundreds, of new messages. Then dozens of notifications in Slack, and GitHub, and Trello, and so on.&lt;/p&gt;
&lt;p&gt;In recent years, I&#39;ve drastically reduced that anxiety by reducing the number of notifications. It&#39;s not perfect, and I&#39;m still working on it, but it has served me well. Here&#39;s what I do. I hope it may help you, too!&lt;/p&gt;
&lt;h2&gt;Make it obvious you are unavailable&lt;/h2&gt;
&lt;p&gt;Set an email auto-responder. Update your status and/or name in the chat app to &amp;quot;out of office until...&amp;quot;. Mark yourself as out-of-office in the calendar and automatically decline all invitations. Announce your absence to colleagues, customers, and whoever else might be trying to contact you.&lt;/p&gt;
&lt;p&gt;This deals with humans. Now onto services and transactional emails.&lt;/p&gt;
&lt;h2&gt;Remove duplicate notifications&lt;/h2&gt;
&lt;p&gt;Turn off emails from third-party services. Most services already have in-application notification systems. If I first see the email and follow the link to the service, that usually clears the in-app notification. If I first see the notification in the app, the email is redundant and will take me time to process later for no good reason.&lt;/p&gt;
&lt;p&gt;I used to think that disabling email notifications would help only during time-off, but I&#39;ve come to appreciate it even during work. It&#39;s resulted in less mindless clicking through emails.&lt;/p&gt;
&lt;p&gt;If you&#39;ve been using email as your only inbox, disabling those notifications will break that process. That brings me to the next point.&lt;/p&gt;
&lt;h2&gt;Multiple inboxes, pull-based vs push-based notifications&lt;/h2&gt;
&lt;p&gt;I turned off emails from GitHub a decade ago. When I am ready to review code, I open the website and look at the notifications page for review requests. Going one step further, I might ignore notifications and go directly to the pull requests page of a repository and decide what to review there.&lt;/p&gt;
&lt;p&gt;On projects using Trello, I&#39;ve found the activity log of the board very valuable. It&#39;s always there and in chronological order, so I can catch up any time. That also makes notifications less important.&lt;/p&gt;
&lt;p&gt;Overall, notifications introduce a sense of urgency. I get interrupted and feel the need to act on the new information. Switching to &amp;quot;pull-based&amp;quot; system removes the time component and gives me more control.&lt;/p&gt;
&lt;h2&gt;Identify what&#39;s worthy of your attention&lt;/h2&gt;
&lt;p&gt;You don&#39;t need to be part of every discussion and make every decision. I&#39;ve found that &amp;quot;out of sight, out of mind&amp;quot; is a good counter to the fear of missing out.&lt;/p&gt;
&lt;p&gt;Sometimes I reply to a Slack thread asking for my input, but many other people do as well. The replies and notifications continue well past the point of any of the new messages being relevant to me. The context menu has an action called &amp;quot;Turn off notifications for replies&amp;quot;. After that, I am free.&lt;/p&gt;
&lt;p&gt;Similarly, I may have contributed to a discussion on GitHub, but have nothing more to add, because others have it covered. I go to the notification settings of the issue or pull request and click &amp;quot;Unsubscribe&amp;quot;. If my participation is still needed, someone will mention me in the discussion and I&#39;ll get a new notification.&lt;/p&gt;
&lt;p&gt;You can mute or leave Slack channels you don&#39;t use. You can stop watching GitHub repositories you don&#39;t work on anymore. It&#39;s okay to let go.&lt;/p&gt;
&lt;h2&gt;Check notification settings for each new service you use&lt;/h2&gt;
&lt;p&gt;Over time you inevitably sign up for new services and the number of emails creeps up again. This is because every company insists on sending emails for every event. They also send marketing emails about product updates.&lt;/p&gt;
&lt;p&gt;I&#39;ve started checking the email settings of every app right after creating an account and disabling communication by email.&lt;/p&gt;
&lt;h2&gt;Mark all as read&lt;/h2&gt;
&lt;p&gt;I&#39;ve rarely done that, because I&#39;m afraid to miss something important. Then again, important things tend to re-surface.&lt;/p&gt;
&lt;h2&gt;Be considerate to others&lt;/h2&gt;
&lt;p&gt;Now that you&#39;re on top of your inbox(es), help others out. My strategy for that is delay and summarise.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Delay.&lt;/strong&gt; Check if the person is working before sending them a message. Try to find a solution on your own. If you cannot, take notes and wait for the person to come back to discuss with them.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Summarise.&lt;/strong&gt; When a colleague comes back to work, write down a few bullet points with the most important things that happened in their absence. Include links to more context if they want to dive into some of the topics. I like to mix all kinds of updates. Here is what that summary might look like, with placeholders:&lt;/p&gt;
&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;[Person] joined the team&lt;/li&gt;
&lt;li&gt;We found a solution to [problem that occurred before you left on holiday], here&#39;s a [link] to the details.&lt;/li&gt;
&lt;li&gt;We are currently working on [new feature]&lt;/li&gt;
&lt;li&gt;[Person] announced [news important to everyone in the company]&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;h2&gt;Further considerations&lt;/h2&gt;
&lt;p&gt;The accumulation of emails and other notifications during time-off was an obvious pain point. While avoiding that pain was the motivation of the actions I described, the outcome for me has been better focus on a daily basis.&lt;/p&gt;
&lt;p&gt;There is more room for improvement. On days when I get too distracted at work, I completely close Slack and the Mail program. That obviously doesn&#39;t reduce the number of messages, but I can read them in batches, the same way I approach code review.&lt;/p&gt;
&lt;p&gt;A further step in that direction would be to disable desktop notifications and only look at my inboxes a few times per day.&lt;/p&gt;
</content>
    
  </entry>
  
    
  
  <entry>
    <title>GitHub pull request merge strategies</title>
    <link href="https://thoughtbot.com/blog/github-pull-request-merge-strategies"/>
    <updated>2023-07-17T00:00:00Z</updated>
    <id>https://thoughtbot.com/blog/github-pull-request-merge-strategies</id>
    
    <content type="html">An overview of what the options provided by the pull request merge button on GitHub do and how to make the best out of each strategy</content>
    
  </entry>
  
    
  
  <entry>
    <title>Rails developers write some Rust: a review of Axum 0.6</title>
    <link href="https://thoughtbot.com/blog/axum-from-a-rails-perspective"/>
    <updated>2023-01-16T00:00:00Z</updated>
    <id>https://thoughtbot.com/blog/axum-from-a-rails-perspective</id>
    
    <content type="html">Is Rust Web yet? We used our Rails knowledge to try writing a Web application in Axum in two days</content>
    
  </entry>
  
    
  
  <entry>
    <title>Trying to prevent errors is good, being able to recover from errors is better</title>
    <link href="https://dimiterpetrov.com/blog/error-prevention-vs-error-recovery/"/>
    <updated>2022-12-05T00:00:00Z</updated>
    <id>https://dimiterpetrov.com/blog/error-prevention-vs-error-recovery/</id>
    
    <content type="html">&lt;p&gt;I&#39;m the person on your team who does not fear deploying half an hour before the end of the workday. Not because I&#39;m reckless, but because I&#39;m confident in the process of recovering from errors. I work in short iterations, developing and deploying small changes one at a time. I have automated tests, code review, production monitoring and error alerts. If the deployment breaks something, it&#39;s easy to revert the last commit and deploy again.&lt;/p&gt;
&lt;p&gt;In my experience, a lot of web-based business software can benefit from more confidence in the release cycle. The more you give into fear of failure, the more you postpone releases. Eventually you are too afraid to break things so you change nothing in production. You test extensively on pre-production environments, which never quite match the real production environment. That takes a long time so your development cadence slows down. Releasing becomes a big event. The team is on edge, because the accumulated changeset is large and many things can go wrong.&lt;/p&gt;
&lt;p&gt;The remedy to this downward spiral is accepting that things will eventually go wrong, no matter how hard you try to prevent that. Learning from incidents will teach you how to react to future incidents and you will become better at recovery.&lt;/p&gt;
&lt;p&gt;Investing in prevention still plays as a big role. Type systems, automated tests, manual tests, feature flags... all of these tools are worth it. Just don&#39;t forget to plan for failure: improve observability, run incident recovery drills, write documentation. Having these conversations in your team and getting everyone involved will help with confidence, release speed and stress levels.&lt;/p&gt;
</content>
    
  </entry>
  
    
  
  <entry>
    <title>Thoughts on onboarding and offboarding</title>
    <link href="https://dimiterpetrov.com/blog/onboarding-offboarding/"/>
    <updated>2022-11-16T00:00:00Z</updated>
    <id>https://dimiterpetrov.com/blog/onboarding-offboarding/</id>
    
    <content type="html">&lt;p&gt;When I&#39;m joining a new organisation, I want to be useful as early as possible. I record what I observe until I get used to the new environment. Chances are my observations can turn into documentation or change of process that will make onboarding easier for the next newcomer. I want to prove myself, to show what I can do and be given responsibility.&lt;/p&gt;
&lt;p&gt;When I&#39;m onboarding somebody else, I want them to feel welcome and heard and seen, to show them we can work together well. I hope to gain their trust, so that they feel safe giving feedback.&lt;/p&gt;
&lt;p&gt;When I&#39;m offboarding someone, I need to pretend to not trust them, even though I do. I follow a checklist to revoke their access to services, information and infrastructure. I make sure that neither they, by negligence, nor a third party, by malice, can harm the organisation with forgotten accounts or elevated privileges.&lt;/p&gt;
&lt;p&gt;When I&#39;m offboarding myself, I try to share my knowledge well in advance, so that nobody needs me when I&#39;m gone. I delete my own accounts and check that nothing breaks and nothing is lost. I give the team time to transition.&lt;/p&gt;
</content>
    
  </entry>
  
    
  
  <entry>
    <title>SVG icon sizing and alignment</title>
    <link href="https://dimiterpetrov.com/blog/svg-icon-sizing-and-alignment/"/>
    <updated>2022-07-25T00:00:00Z</updated>
    <id>https://dimiterpetrov.com/blog/svg-icon-sizing-and-alignment/</id>
    
    <content type="html">&lt;p&gt;Making SVG icons look right alongside text in web applications can be tricky. I&#39;m writing down the techniques that have worked well for me, because I tend to forget them by the time I need them again.&lt;/p&gt;
&lt;p&gt;For me it&#39;s about:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;preserving the icon&#39;s aspect ratio and internal spacing,&lt;/li&gt;
&lt;li&gt;scaling the icon proportionally to the text,&lt;/li&gt;
&lt;li&gt;vertically aligning the icon to the text.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Let&#39;s how these come together.&lt;/p&gt;
&lt;h2&gt;Icon proportions in isolation from accompanying text&lt;/h2&gt;
&lt;p&gt;The &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/viewBox&quot;&gt;&lt;code&gt;viewBox&lt;/code&gt;&lt;/a&gt; property defines the viewport into the SVG image. With it you can shift and crop as you like.&lt;/p&gt;
&lt;p&gt;Consider a disc positioned at coordinates (10, 10) with a radius of 10.&lt;/p&gt;
&lt;pre class=&quot;language-svg&quot;&gt;&lt;code class=&quot;language-svg&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;svg&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;circle&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;cx&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;10&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;cy&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;10&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;10&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;svg&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;By varying the &lt;code&gt;viewBox&lt;/code&gt; we can change the perceived zoom level and offset.&lt;/p&gt;
&lt;div style=&quot;display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); text-align: center&quot;&gt;
  &lt;div&gt;
    &lt;figure&gt;
      &lt;svg width=&quot;150&quot; height=&quot;150&quot; style=&quot;height: 100%; border: 2px solid lavender&quot; viewBox=&quot;10 5 15 15&quot; fill=&quot;deeppink&quot;&gt;
        &lt;circle cx=&quot;10&quot; cy=&quot;10&quot; r=&quot;10&quot;&gt;
      &lt;/circle&gt;&lt;/svg&gt;
      &lt;figcaption&gt;
        viewBox=&quot;10 5 15 15&quot;
      &lt;/figcaption&gt;
    &lt;/figure&gt;
  &lt;/div&gt;
  &lt;div&gt;
    &lt;figure&gt;
      &lt;svg width=&quot;150&quot; height=&quot;150&quot; style=&quot;height: 100%; border: 2px solid lavender&quot; viewBox=&quot;-5 -5 30 30&quot; fill=&quot;deeppink&quot;&gt;
        &lt;circle cx=&quot;10&quot; cy=&quot;10&quot; r=&quot;10&quot;&gt;
      &lt;/circle&gt;&lt;/svg&gt;
      &lt;figcaption&gt;
        viewBox=&quot;-5 -5 30 30&quot;
      &lt;/figcaption&gt;
    &lt;/figure&gt;
  &lt;/div&gt;
  &lt;div&gt;
    &lt;figure&gt;
      &lt;svg width=&quot;150&quot; height=&quot;150&quot; style=&quot;height: 100%; border: 2px solid lavender&quot; viewBox=&quot;0 0 20 20&quot; fill=&quot;deeppink&quot;&gt;
        &lt;circle cx=&quot;10&quot; cy=&quot;10&quot; r=&quot;10&quot;&gt;
      &lt;/circle&gt;&lt;/svg&gt;
      &lt;figcaption&gt;
        viewBox=&quot;0 0 20 20&quot; 
      &lt;/figcaption&gt;
    &lt;/figure&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Icon designers visually balance this framing to achieve harmony within the icon set. For example the following icons come from the same set (&lt;a href=&quot;https://heroicons.com/&quot;&gt;Heroicons&lt;/a&gt;) but have different spacing.&lt;/p&gt;
&lt;figure&gt;
  &lt;div style=&quot;display: grid; gap: 1rem; grid-template-columns: repeat(auto-fit, minmax(50px, 1fr)); color: slategray&quot;&gt;
    &lt;svg width=&quot;20&quot; height=&quot;20&quot; style=&quot;height: 100%; width: 100%; border: 2px solid lavender&quot; viewBox=&quot;0 0 20 20&quot; fill=&quot;currentColor&quot;&gt;
      &lt;title&gt;Search icon&lt;/title&gt;
      &lt;path fill-rule=&quot;evenodd&quot; d=&quot;M8 4a4 4 0 100 8 4 4 0 000-8zM2 8a6 6 0 1110.89 3.476l4.817 4.817a1 1 0 01-1.414 1.414l-4.816-4.816A6 6 0 012 8z&quot; clip-rule=&quot;evenodd&quot;&gt;&lt;/path&gt;
    &lt;/svg&gt;
    &lt;svg width=&quot;20&quot; height=&quot;20&quot; style=&quot;height: 100%; width: 100%; border: 2px solid lavender&quot; viewBox=&quot;0 0 20 20&quot; fill=&quot;currentColor&quot;&gt;
      &lt;title&gt;Right chevron icon&lt;/title&gt;
      &lt;path fill-rule=&quot;evenodd&quot; d=&quot;M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z&quot; clip-rule=&quot;evenodd&quot;&gt;&lt;/path&gt;
    &lt;/svg&gt;
    &lt;svg width=&quot;20&quot; height=&quot;20&quot; style=&quot;height: 100%; width: 100%; border: 2px solid lavender&quot; viewBox=&quot;0 0 20 20&quot; fill=&quot;currentColor&quot;&gt;
      &lt;title&gt;Folder icon&lt;/title&gt;
      &lt;path d=&quot;M2 6a2 2 0 012-2h5l2 2h5a2 2 0 012 2v6a2 2 0 01-2 2H4a2 2 0 01-2-2V6z&quot;&gt;&lt;/path&gt;
    &lt;/svg&gt;
  &lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;Without the &lt;code&gt;viewBox&lt;/code&gt; information, the SVG images may appear at the wrong dimension, e.g. with the wrong &amp;quot;zoom level&amp;quot; and/or the wrong aspect ratio. If your icons don&#39;t have this property consider adding it yourself. I recommend a square aspect ratio and starting with no space between the shape and its bounding box. Then you can tweak the spacing and alignment within the SVG.&lt;/p&gt;
&lt;h2&gt;Proportional text and icons&lt;/h2&gt;
&lt;p&gt;When it comes to icon size, I first set a fallback using the &lt;code&gt;width&lt;/code&gt; and &lt;code&gt;height&lt;/code&gt; attributes directly on the SVG. That way if the CSS doesn&#39;t load for some reason we don&#39;t end up with the browser default, which is to render SVG images really large. I set a reasonable size, keeping the square aspect ratio, for example &lt;code&gt;width=&amp;quot;20&amp;quot; height=&amp;quot;20&amp;quot;&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Then I override the size fallback with CSS rules (or utility classes) to make the SVG image take as much space as its parent element:&lt;/p&gt;
&lt;pre class=&quot;language-css&quot;&gt;&lt;code class=&quot;language-css&quot;&gt;&lt;span class=&quot;token selector&quot;&gt;svg&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;height&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 100%&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;width&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 100%&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now I can resize the icon as needed in different contexts without modifying the SVG source or targeting the icon specifically with CSS rules.&lt;/p&gt;
&lt;p&gt;For example, if I&#39;m making a form button containing the search icon:&lt;/p&gt;
&lt;figure&gt;
  &lt;button style=&quot;display: flex; align-items: center; gap: 0.5rem; padding: 0.5rem; font-size: 1rem&quot;&gt;
    &lt;div style=&quot;height: 1rem; width: 1rem;&quot;&gt;
      &lt;svg width=&quot;20&quot; height=&quot;20&quot; style=&quot;height: 100%; width: 100%;&quot; viewBox=&quot;0 0 20 20&quot; fill=&quot;currentColor&quot;&gt;
        &lt;path fill-rule=&quot;evenodd&quot; d=&quot;M8 4a4 4 0 100 8 4 4 0 000-8zM2 8a6 6 0 1110.89 3.476l4.817 4.817a1 1 0 01-1.414 1.414l-4.816-4.816A6 6 0 012 8z&quot; clip-rule=&quot;evenodd&quot;&gt;&lt;/path&gt;
      &lt;/svg&gt;
    &lt;/div&gt;
    Search
  &lt;/button&gt;
&lt;/figure&gt;
&lt;p&gt;I would wrap the icon in a &lt;code&gt;div&lt;/code&gt; and set the &lt;code&gt;div&lt;/code&gt; element&#39;s height (inline style for illustration here, I would do it differently in real application code).&lt;/p&gt;
&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;button&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;submit&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt; &lt;span class=&quot;token special-attr&quot;&gt;&lt;span class=&quot;token attr-name&quot;&gt;style&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;token value css language-css&quot;&gt;&lt;span class=&quot;token property&quot;&gt;height&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 1rem&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token property&quot;&gt;width&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 1rem&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;&amp;lt;!-- search icon rendered here --&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  Search
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;button&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The key is to use units proportional to the text size, like &lt;code&gt;em&lt;/code&gt; or &lt;code&gt;rem&lt;/code&gt;, for the height of the wrapper element. That way the icon scales with the text.&lt;/p&gt;
&lt;h2&gt;Aligning icons with multi-line text&lt;/h2&gt;
&lt;p&gt;For the button above, I centered the icon and text vertically in the button using flexbox.&lt;/p&gt;
&lt;pre class=&quot;language-css&quot;&gt;&lt;code class=&quot;language-css&quot;&gt;&lt;span class=&quot;token selector&quot;&gt;button[type=&quot;submit&quot;]&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;display&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; inline-flex&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;align-items&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; center&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This works well with a single line of text, but may not be what you want when the text spans multiple lines.&lt;/p&gt;
&lt;p&gt;In the following example, the icon is centered vertically.&lt;/p&gt;
&lt;figure&gt;
  &lt;div style=&quot;max-width: max-content; border: 2px solid lavender; padding: 1rem&quot;&gt;
    &lt;div style=&quot;display: flex; gap: 0.5rem; align-items: center&quot;&gt;
      &lt;div style=&quot;flex-shrink: 0; display: flex; height: 1em; width: 1em;&quot;&gt;
        &lt;svg width=&quot;20&quot; height=&quot;20&quot; style=&quot;height: 100%; width: 100%;&quot; viewBox=&quot;0 0 20 20&quot; fill=&quot;currentColor&quot;&gt;
          &lt;path fill-rule=&quot;evenodd&quot; d=&quot;M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z&quot; clip-rule=&quot;evenodd&quot;&gt;&lt;/path&gt;
        &lt;/svg&gt;
      &lt;/div&gt;
      &lt;div&gt;
        &lt;div style=&quot;font-weight: bold;&quot;&gt;Message title&lt;/div&gt;
        &lt;div&gt;
          A long and detailed message description.
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
  &lt;figcaption&gt;
    Flex children aligned with &lt;code&gt;align-items: center&lt;/code&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;What if we wanted to align it with the first line? In my opinion both &lt;code&gt;align-items: start&lt;/code&gt; and &lt;code&gt;align-items: baseline&lt;/code&gt; look off.&lt;/p&gt;
&lt;div style=&quot;display: grid; grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));&quot;&gt;
  &lt;figure&gt;
    &lt;div style=&quot;max-width: max-content; border: 2px solid lavender; padding: 1rem&quot;&gt;
      &lt;div style=&quot;display: flex; gap: 0.5rem; align-items: start&quot;&gt;
        &lt;div style=&quot;flex-shrink: 0; display: flex; height: 1em; width: 1em;&quot;&gt;
          &lt;svg width=&quot;20&quot; height=&quot;20&quot; style=&quot;height: 100%; width: 100%;&quot; viewBox=&quot;0 0 20 20&quot; fill=&quot;currentColor&quot;&gt;
            &lt;path fill-rule=&quot;evenodd&quot; d=&quot;M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z&quot; clip-rule=&quot;evenodd&quot;&gt;&lt;/path&gt;
          &lt;/svg&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div style=&quot;font-weight: bold;&quot;&gt;Message title&lt;/div&gt;
          &lt;div&gt;
            A long and detailed message description.
          &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    &lt;figcaption&gt;
      Aligned with &lt;code&gt;align-items: start&lt;/code&gt;
    &lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;figure&gt;
    &lt;div style=&quot;max-width: max-content; border: 2px solid lavender; padding: 1rem&quot;&gt;
      &lt;div style=&quot;display: flex; gap: 0.5rem; align-items: baseline&quot;&gt;
        &lt;div style=&quot;flex-shrink: 0; display: flex; height: 1em; width: 1em;&quot;&gt;
          &lt;svg width=&quot;20&quot; height=&quot;20&quot; style=&quot;height: 100%; width: 100%;&quot; viewBox=&quot;0 0 20 20&quot; fill=&quot;currentColor&quot;&gt;
            &lt;path fill-rule=&quot;evenodd&quot; d=&quot;M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z&quot; clip-rule=&quot;evenodd&quot;&gt;&lt;/path&gt;
          &lt;/svg&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div style=&quot;font-weight: bold;&quot;&gt;Message title&lt;/div&gt;
          &lt;div&gt;
            A long and detailed message description.
          &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    &lt;figcaption&gt;
      Aligned with &lt;code&gt;align-items: baseline&lt;/code&gt;
    &lt;/figcaption&gt;
  &lt;/figure&gt;
&lt;/div&gt;
&lt;p&gt;The trick is to have a sibling text node to which you can centrally align the icon (like in the single line case), but then use baseline alignment on the parent, so that the text from the two columns aligns. Let&#39;s visualize that before getting to the code:&lt;/p&gt;
&lt;figure&gt;
  &lt;div style=&quot;max-width: max-content; border: 2px solid lavender; padding: 1rem&quot;&gt;
    &lt;div style=&quot;display: flex; gap: 0.5rem; align-items: baseline&quot;&gt;
      &lt;div style=&quot;flex-shrink: 0; display: inline-flex; align-items: center; outline: 1px solid red&quot;&gt;
        reference&amp;nbsp;
        &lt;div style=&quot;display: flex; height: 1em; width: 1em&quot;&gt;
          &lt;svg width=&quot;20&quot; height=&quot;20&quot; style=&quot;height: 100%; width: 100%;&quot; viewBox=&quot;0 0 20 20&quot; fill=&quot;currentColor&quot;&gt;
            &lt;path fill-rule=&quot;evenodd&quot; d=&quot;M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z&quot; clip-rule=&quot;evenodd&quot;&gt;&lt;/path&gt;
          &lt;/svg&gt;
        &lt;/div&gt;
      &lt;/div&gt;
      &lt;div style=&quot;outline: 1px solid red&quot;&gt;
        &lt;div style=&quot;font-weight: bold; outline: 2px dotted red&quot;&gt;Message title&lt;/div&gt;
        &lt;div&gt;
          A long and detailed message description.
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
  &lt;figcaption&gt;
    Two columns aligned with &lt;code&gt;align-items: baseline&lt;/code&gt; and an icon aligned to the text in the first column with &lt;code&gt;align-items: center&lt;/code&gt;
  &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;The icon is centrally aligned to the text which says &amp;quot;reference&amp;quot;. &amp;quot;reference&amp;quot; is baseline-aligned to &amp;quot;Message title&amp;quot;.&lt;/p&gt;
&lt;p&gt;Of course you don&#39;t want actual text next to the icon. The trick is to use a &lt;a href=&quot;https://en.wikipedia.org/wiki/Zero-width_space&quot;&gt;zero-width space&lt;/a&gt;, which you can insert by writing its HTML entity: &lt;code&gt;&amp;amp;#x200B;&lt;/code&gt;. Normal white space does not work because HTML parsers strip that.&lt;/p&gt;
&lt;p&gt;Check out the simplified code. (Again the styles are inline to make it easier to understand what is going on)&lt;/p&gt;
&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt; &lt;span class=&quot;token special-attr&quot;&gt;&lt;span class=&quot;token attr-name&quot;&gt;style&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;token value css language-css&quot;&gt;&lt;span class=&quot;token property&quot;&gt;display&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; flex&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token property&quot;&gt;align-items&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; baseline&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt; &lt;span class=&quot;token special-attr&quot;&gt;&lt;span class=&quot;token attr-name&quot;&gt;style&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;token value css language-css&quot;&gt;&lt;span class=&quot;token property&quot;&gt;display&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; flex&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token property&quot;&gt;align-items&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; center&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token entity&quot; title=&quot;&amp;#x200B;&quot;&gt;&amp;amp;#x200B;&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;&amp;lt;!-- zero-width space, for alignment --&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt; &lt;span class=&quot;token special-attr&quot;&gt;&lt;span class=&quot;token attr-name&quot;&gt;style&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;token value css language-css&quot;&gt;&lt;span class=&quot;token property&quot;&gt;height&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 1em&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token property&quot;&gt;width&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 1em&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;&amp;lt;!-- SVG icon goes here, omitted for brevity --&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;&amp;lt;!-- text from the right column goes here --&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here&#39;s the result:&lt;/p&gt;
&lt;figure&gt;
  &lt;div style=&quot;max-width: max-content; border: 2px solid lavender; padding: 1rem&quot;&gt;
    &lt;div style=&quot;display: flex; gap: 0.5rem; align-items: baseline&quot;&gt;
      &lt;div style=&quot;flex-shrink: 0; display: inline-flex; align-items: center;&quot;&gt;
        &amp;#x200B;
        &lt;div style=&quot;display: flex; height: 1em; width: 1em&quot;&gt;
          &lt;svg width=&quot;20&quot; height=&quot;20&quot; style=&quot;height: 100%; width: 100%;&quot; viewBox=&quot;0 0 20 20&quot; fill=&quot;currentColor&quot;&gt;
            &lt;path fill-rule=&quot;evenodd&quot; d=&quot;M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z&quot; clip-rule=&quot;evenodd&quot;&gt;&lt;/path&gt;
          &lt;/svg&gt;
        &lt;/div&gt;
      &lt;/div&gt;
      &lt;div&gt;
        &lt;div style=&quot;font-weight: bold;&quot;&gt;Message title&lt;/div&gt;
        &lt;div&gt;
          A long and detailed message description.
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;It&#39;s easier to compare with larger icons:&lt;/p&gt;
&lt;div style=&quot;display: grid; grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));&quot;&gt;
  &lt;figure&gt;
    &lt;div style=&quot;max-width: max-content; border: 2px solid lavender; padding: 1rem&quot;&gt;
      &lt;div style=&quot;display: flex; gap: 0.5rem; align-items: baseline&quot;&gt;
        &lt;div style=&quot;flex-shrink: 0; display: flex; height: 1.5em; width: 1.5em;&quot;&gt;
          &lt;svg width=&quot;20&quot; height=&quot;20&quot; style=&quot;height: 100%; width: 100%;&quot; viewBox=&quot;0 0 20 20&quot; fill=&quot;currentColor&quot;&gt;
            &lt;path fill-rule=&quot;evenodd&quot; d=&quot;M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z&quot; clip-rule=&quot;evenodd&quot;&gt;&lt;/path&gt;
          &lt;/svg&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div style=&quot;font-weight: bold;&quot;&gt;Message title&lt;/div&gt;
          &lt;div&gt;
            A long and detailed message description.
          &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    &lt;figcaption&gt;
      Aligned with &lt;code&gt;align-items: baseline&lt;/code&gt;
    &lt;/figcaption&gt;
  &lt;/figure&gt;
  &lt;figure&gt;
    &lt;div style=&quot;max-width: max-content; border: 2px solid lavender; padding: 1rem&quot;&gt;
      &lt;div style=&quot;display: flex; gap: 0.5rem; align-items: baseline&quot;&gt;
        &lt;div style=&quot;flex-shrink: 0; display: inline-flex; align-items: center;&quot;&gt;
          &amp;#x200B;
          &lt;div style=&quot;display: flex; height: 1.5em; width: 1.5em&quot;&gt;
            &lt;svg width=&quot;20&quot; height=&quot;20&quot; style=&quot;height: 100%; width: 100%;&quot; viewBox=&quot;0 0 20 20&quot; fill=&quot;currentColor&quot;&gt;
              &lt;path fill-rule=&quot;evenodd&quot; d=&quot;M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z&quot; clip-rule=&quot;evenodd&quot;&gt;&lt;/path&gt;
            &lt;/svg&gt;
          &lt;/div&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div style=&quot;font-weight: bold;&quot;&gt;Message title&lt;/div&gt;
          &lt;div&gt;
            A long and detailed message description.
          &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    &lt;figcaption&gt;
      Aligned with &lt;code&gt;align-items: center&lt;/code&gt; at the inner level and &lt;code&gt;align-items: baseline&lt;/code&gt; at the outer level.
    &lt;/figcaption&gt;
  &lt;/figure&gt;
&lt;/div&gt;
&lt;p&gt;In my projects, I usually encapsulate the necessary markup in a function or component. For example, I have this view helper in a Rails project (the utility classes are from Tailwind CSS):&lt;/p&gt;
&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token function&quot;&gt;inline_icon&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;icon_name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&#39;w-4&#39;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  capture &lt;span class=&quot;token keyword&quot;&gt;do&lt;/span&gt;
    content_tag&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token symbol&quot;&gt;:div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&#39;flex items-center&#39;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;do&lt;/span&gt;
      concat &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&#39;&amp;amp;#x200B;&#39;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;html_safe &lt;span class=&quot;token comment&quot;&gt;# zero-width space character to set the correct height (in combination with a 100% height on the svg icon itself)&lt;/span&gt;
      concat &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;content_tag&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token symbol&quot;&gt;:div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; binding&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;local_variable_get&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token symbol&quot;&gt;:class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;do&lt;/span&gt;
        render &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;icons/&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;token content&quot;&gt;icon_name&lt;/span&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
</content>
    
  </entry>
  
    
  
  <entry>
    <title>Working with constant interruptions</title>
    <link href="https://dimiterpetrov.com/blog/working-with-constant-interruptions/"/>
    <updated>2022-05-12T00:00:00Z</updated>
    <id>https://dimiterpetrov.com/blog/working-with-constant-interruptions/</id>
    
    <content type="html">&lt;p&gt;In 2018 I chose to become an engineering manager. It was clear to me that good teams, like good code bases, require constant work. Only, I wasn&#39;t just interested in shaping my team. I wanted to influence how the company worked in general, all with the top-level goal of creating a good environment for myself.&lt;/p&gt;
&lt;p&gt;I started getting involved in hiring, sales pitches, writing offers, strategic meetings and everything else that makes a software agency tick. Simultaneously I was on multiple client projects, leading one or two myself.&lt;/p&gt;
&lt;p&gt;Unsurprisingly, focusing on anything for more than 2 hours became impossible. I somehow adapted and this is how I&#39;ve been working ever since.&lt;/p&gt;
&lt;h2&gt;Why I&#39;m writing this&lt;/h2&gt;
&lt;p&gt;I can&#39;t pretend that there is some wisdom to glean. Rather, I want to record where I stand after a few years of working in such a scattered way. I hope to reflect on that in the future and see what has changed.&lt;/p&gt;
&lt;h2&gt;Facts and feelings&lt;/h2&gt;
&lt;p&gt;These may not be all be true right now, but have occurred at various times.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;It feels like I have very little time during a given day, yet I sometimes don&#39;t remember what I&#39;ve done by the end of it.&lt;/li&gt;
&lt;li&gt;I get interrupted a lot and I&#39;m often multitasking.&lt;/li&gt;
&lt;li&gt;I spend 4-5 hours per week in 1-on-1s and 3-6 hours on other regularly scheduled meetings.&lt;/li&gt;
&lt;li&gt;I review between 50 and 100 pull requests a month according to GitHub.&lt;/li&gt;
&lt;li&gt;People say &amp;quot;I know you&#39;re busy so I&#39;ll try to not take too much of your time&amp;quot;. I&#39;d much rather dedicate time to them than the dozens other things on my list that I have to do alone.&lt;/li&gt;
&lt;li&gt;The term &amp;quot;&lt;a href=&quot;https://noidea.dog/glue&quot;&gt;glue work&lt;/a&gt;&amp;quot; describes a lot of what I do.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;How I still get things done&lt;/h2&gt;
&lt;p&gt;I knew in theory about the benefits of breaking down tasks. Now I also do it because of necessity. I try to make everything achievable in small chunks. For example:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;I encourage developers to make smaller pull requests that can be reviewed in only a few minutes.&lt;/li&gt;
&lt;li&gt;Most of my writing happens in 3 phases, spread from hours to months apart: i) notes captured as quickly as possible, ii) fleshing out the text, iii) a final edit and review.&lt;/li&gt;
&lt;li&gt;I deliver improvements to projects in tiny steps. That aligns well with my development philosophy and is easy to do with feature flags.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I still reserve time for learning, thinking and experimentation, though mostly outside normal work hours. Because of that I am careful to invest in learning what is interesting and useful to me. If it also benefits my employer, I record the time spent as work time and use my training/education budget.&lt;/p&gt;
&lt;p&gt;Whether with normal work or experiments, my plans are often ahead of the execution and I don&#39;t have time for everything. That forces me to make a compelling case for my ideas and get other people to help.&lt;/p&gt;
&lt;h2&gt;What I want to change&lt;/h2&gt;
&lt;p&gt;I am doing some of these already, but I&#39;d like to go further.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;I&#39;d like to get better at delegation.&lt;/li&gt;
&lt;li&gt;I hope that by spending more time on documentation, I will spend less time on repeating myself.&lt;/li&gt;
&lt;li&gt;I want to take more time to mentor others, even when it doesn&#39;t &amp;quot;scale&amp;quot;, because I really like teaching.&lt;/li&gt;
&lt;li&gt;Generally, I want to make myself replaceable, so that I can focus on what matters to me.&lt;/li&gt;
&lt;/ul&gt;
</content>
    
  </entry>
  
    
  
  <entry>
    <title>Clean your backlog</title>
    <link href="https://blog.simplificator.com/2021/11/10/clean-your-backlog/"/>
    <updated>2021-11-10T00:00:00Z</updated>
    <id>https://blog.simplificator.com/2021/11/10/clean-your-backlog/</id>
    
    <content type="html">The problem with big backlogs in software projects and how to keep the backlog small</content>
    
  </entry>
  
    
  
  <entry>
    <title>Tips for applying to a job at a small company</title>
    <link href="https://blog.simplificator.com/2021/10/12/tips-for-applying-to-a-job-at-a-small-company/"/>
    <updated>2021-10-12T00:00:00Z</updated>
    <id>https://blog.simplificator.com/2021/10/12/tips-for-applying-to-a-job-at-a-small-company/</id>
    
    <content type="html">Advice and encouragement for job applicants reflecting the Simplificator hiring process back in 2021</content>
    
  </entry>
  
    
  
  <entry>
    <title>How to connect to an Apple Thunderbolt display from Linux</title>
    <link href="https://dimiterpetrov.com/blog/linux-thunderbolt-display-connection/"/>
    <updated>2021-09-20T00:00:00Z</updated>
    <id>https://dimiterpetrov.com/blog/linux-thunderbolt-display-connection/</id>
    
    <content type="html">&lt;p&gt;Although the &lt;a href=&quot;https://en.wikipedia.org/wiki/Apple_Thunderbolt_Display&quot;&gt;Apple Thunderbolt Display&lt;/a&gt; is discontinued, it&#39;s still a good monitor and can serve as a docking station. It has a serviceable webcam, loudspeakers, an Ethernet port and USB 2 ports.&lt;/p&gt;
&lt;p&gt;The Linux kernel supports Thunderbolt. After connecting the device, check if it&#39;s listed by &lt;code&gt;lspci&lt;/code&gt;. If it&#39;s not, you may have to enable &amp;quot;Thunderbolt assist mode&amp;quot; in the BIOS (at least for Thinkpads).&lt;/p&gt;
&lt;p&gt;The Thunderbolt device manager for user space is called &lt;a href=&quot;https://gitlab.freedesktop.org/bolt/bolt&quot;&gt;&lt;code&gt;bolt&lt;/code&gt;&lt;/a&gt;. &lt;code&gt;boltctl&lt;/code&gt; allows you to list Thunderbolt devices and authorize them. While the screen itself may work without special action, the webcam and Ethernet require authorization.&lt;/p&gt;
&lt;p&gt;One time authorization is done with &lt;code&gt;boltctl authorize&lt;/code&gt;. &lt;code&gt;boltctl enroll&lt;/code&gt; remembers the device and automatically authorizes it at future connections. Check the &lt;code&gt;boltctl&lt;/code&gt; manual (&lt;code&gt;man boltctl&lt;/code&gt;) and the &lt;a href=&quot;https://www.kernel.org/doc/html/v5.0/admin-guide/thunderbolt.html&quot;&gt;Linux kernel Thunderbolt documentation&lt;/a&gt;.&lt;/p&gt;
</content>
    
  </entry>
  
    
  
  <entry>
    <title>A subtle change in PDF output</title>
    <link href="https://blog.simplificator.com/2021/07/05/a-subtle-change-in-pdf-output/"/>
    <updated>2021-07-05T00:00:00Z</updated>
    <id>https://blog.simplificator.com/2021/07/05/a-subtle-change-in-pdf-output/</id>
    
    <content type="html">A debugging story of rendering differences in PDFs after changing application hosting infrastructure</content>
    
  </entry>
  
    
  
  <entry>
    <title>The trade-offs of two feature flag implementations</title>
    <link href="https://dimiterpetrov.com/blog/the-trade-offs-of-two-feature-flag-implementations/"/>
    <updated>2020-12-30T00:00:00Z</updated>
    <id>https://dimiterpetrov.com/blog/the-trade-offs-of-two-feature-flag-implementations/</id>
    
    <content type="html">&lt;p&gt;There is a lot of value in testing work-in-progress features in production. You discover edge cases by using real data and you can measure the performance impact in a production setting.&lt;/p&gt;
&lt;p&gt;Of course, testing in production doesn&#39;t necessarily mean releasing an unfinished feature to all users. This is where feature flags come in, allowing you to enable or disable functionality for specific users or groups (say admins or testers).&lt;/p&gt;
&lt;p&gt;I often implement feature flags from scratch, as the feature flag libraries out there do a lot I don&#39;t need. I usually don&#39;t need a user interface, or to store configuration in Redis, or to let non-developers toggle flags.&lt;/p&gt;
&lt;p&gt;In one case, I thought we might need more complex scenarios in the future, so I started by persisting the feature flags in a database table. 5 years later, I realised we had never taken advantage of the extra flexibility and migrated the flags to static configuration. It proved harder than anticipated.&lt;/p&gt;
&lt;h2&gt;Feature flags in the database&lt;/h2&gt;
&lt;p&gt;When the configuration is in the database, you can potentially change it anytime. That also makes testing easier.&lt;/p&gt;
&lt;p&gt;Consider this piece of production code:&lt;/p&gt;
&lt;pre class=&quot;language-erb&quot;&gt;&lt;code class=&quot;language-erb&quot;&gt;&lt;span class=&quot;token erb language-erb&quot;&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;&amp;lt;%&lt;/span&gt;&lt;span class=&quot;token ruby language-ruby&quot;&gt; &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; Feature&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;enabled&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token symbol&quot;&gt;:facebook_like&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;/span&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;%&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;div&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;fb-like&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token erb language-erb&quot;&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;&amp;lt;%&lt;/span&gt;&lt;span class=&quot;token ruby language-ruby&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# the like button is here &lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;%&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;div&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token erb language-erb&quot;&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;&amp;lt;%&lt;/span&gt;&lt;span class=&quot;token ruby language-ruby&quot;&gt; &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt; &lt;/span&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;%&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Tests can verify different scenarios by updating the feature flags table in the database.&lt;/p&gt;
&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;scenario &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&#39;User sees a like button when enabled&#39;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;do&lt;/span&gt;
  user &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; users&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token symbol&quot;&gt;:alice&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

  visit profile_path&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;user&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  refute_css&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&#39;.fb-like&#39;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

  Feature&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;create&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token symbol&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&#39;facebook_like&#39;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  visit profile_path&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;user&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  assert_css&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&#39;.fb-like&#39;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;Feature.create!(name: &#39;facebook_like&#39;)&lt;/code&gt; is what enables the flag. The test depends on an implementation detail (the flag is stored in the database), but that could be solved by a helper function. Overall it&#39;s very easy to enable or disable flags in tests.&lt;/p&gt;
&lt;p&gt;There are a few downsides of this approach outside of the test environment.&lt;/p&gt;
&lt;p&gt;Flags are disabled by default, which is a good safeguard. However that also means that you must remember to update the database after deploying code that uses a flag. That is a manual step for each deployment environment.&lt;/p&gt;
&lt;p&gt;Additionally, reading flags incurs database reads, which makes response times slower.&lt;/p&gt;
&lt;h2&gt;Feature flags in a file&lt;/h2&gt;
&lt;p&gt;Storing flags in the application configuration makes the deployment story easier. The flag goes through code review and is then automatically updated during deployment.&lt;/p&gt;
&lt;p&gt;As the configuration is under version control, you can also easily debug. When an error occurs in a release, you can check which flags are enabled for that release. If the flags are in the database, you may not have this information anymore unless you implemented an audit trail or include feature flag configuration in the error notification context.&lt;/p&gt;
&lt;p&gt;Reading flags is also faster. You can store flags directly in a data structure in a class, or you can store them in a file and read that file once when the application starts. Then the flags are always in memory.&lt;/p&gt;
&lt;p&gt;That is great for production, but makes testing harder. Let&#39;s look at a slightly modified version of the test from earlier:&lt;/p&gt;
&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;scenario &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&#39;User sees a like button when enabled&#39;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;do&lt;/span&gt;
  user &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; users&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token symbol&quot;&gt;:alice&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

  visit profile_path&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;user&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  refute_css&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&#39;.fb-like&#39;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

  with_feature_enabled&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&#39;facebook_like&#39;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;do&lt;/span&gt;
    visit profile_path&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;user&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    assert_css&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&#39;.fb-like&#39;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;with_feature_enabled&lt;/code&gt; needs to modify the value of the flag for the scope of the block. As I said, the feature flag value is normally read once at application start and cannot be modified anymore. In a unit test I would override configuration by passing it as a constructor parameter. In an integration test as the one above, that&#39;s not possible.&lt;/p&gt;
&lt;p&gt;That leads us to metaprogramming shenanigans.&lt;/p&gt;
&lt;h2&gt;Stubs&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;Feature.enabled?&lt;/code&gt; can be called from any part of the code. The dirty secret to making the test above pass is to &lt;em&gt;conditionally&lt;/em&gt; stub &lt;code&gt;enabled?&lt;/code&gt; for the duration of a block.&lt;/p&gt;
&lt;p&gt;Why conditionally? Because we have to take into account the name of the flag. Let&#39;s say we had this structure:&lt;/p&gt;
&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; Feature&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;enabled&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token symbol&quot;&gt;:feature1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;# something&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; Feature&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;enabled&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token symbol&quot;&gt;:feature2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;# ... something extra&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We might want to test with &lt;code&gt;feature1&lt;/code&gt; enabled, but &lt;code&gt;feature2&lt;/code&gt; disabled, so we cannot return the same value for any call to &lt;code&gt;Feature.enabled?&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Minitest#stub&lt;/code&gt; can help us. It takes three arguments: the method name, a &amp;quot;value or callable&amp;quot; and a block. From the documentation:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Add a temporary stubbed method replacing &lt;code&gt;name&lt;/code&gt; for the duration
of the &lt;code&gt;block&lt;/code&gt;. If &lt;code&gt;val_or_callable&lt;/code&gt; responds to &lt;code&gt;#call&lt;/code&gt;, then it
returns the result of calling it, otherwise returns the value
as-is. If stubbed method yields a block, &lt;code&gt;block_args&lt;/code&gt; will be
passed along. Cleans up the stub at the end of the &lt;code&gt;block&lt;/code&gt;. The
method &lt;code&gt;name&lt;/code&gt; must exist before stubbing.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The unconditional stub would pass a value as the second argument:&lt;/p&gt;
&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;Feature&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;stub &lt;span class=&quot;token symbol&quot;&gt;:enabled?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;do&lt;/span&gt;
 &lt;span class=&quot;token comment&quot;&gt;# Feature.enabled?(:feature1) returns true here&lt;/span&gt;
 &lt;span class=&quot;token comment&quot;&gt;# So does Feature.enabled?(:feature2)&lt;/span&gt;
 &lt;span class=&quot;token comment&quot;&gt;# In fact, all feature flags are enabled now&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We can make the returned value depend on the parameter with a Proc (i.e. a &amp;quot;callable&amp;quot;):&lt;/p&gt;
&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;callable &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Proc&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;arg&lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;
  arg&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;to_s &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;feature1&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;

Feature&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;stub &lt;span class=&quot;token symbol&quot;&gt;:enabled?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; callable &lt;span class=&quot;token keyword&quot;&gt;do&lt;/span&gt;
 &lt;span class=&quot;token comment&quot;&gt;# calling Feature.enabled?(:feature1) returns true&lt;/span&gt;
 &lt;span class=&quot;token comment&quot;&gt;# calling Feature.enabled?(:feature2) returns false&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This code enables a certain flag, but disables all others. What we want is to control one flag, but &amp;quot;pass through&amp;quot; to the default configuration for any other flags.&lt;/p&gt;
&lt;p&gt;Taking inspiration from the Minitest stub implementation itself, we can keep a copy of the original &lt;code&gt;Feature.enabled?&lt;/code&gt; method:&lt;/p&gt;
&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;metaclass &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Feature&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;instance_eval &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
metaclass&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;alias_method &lt;span class=&quot;token symbol&quot;&gt;:original_enabled?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;:enabled?&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We saved the method as &lt;code&gt;Feature.original_enabled?&lt;/code&gt;. That allows us to use it in the &amp;quot;callable&amp;quot; argument once the &lt;code&gt;Feature.enabled?&lt;/code&gt; method is overwritten by the stub:&lt;/p&gt;
&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;callable &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Proc&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;arg&lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; arg&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;to_s &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;feature1&quot;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt;
    Feature&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;original_enabled&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;arg&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;

Feature&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;stub &lt;span class=&quot;token symbol&quot;&gt;:enabled?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; callable &lt;span class=&quot;token keyword&quot;&gt;do&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;# Feature.enabled?(:feature1) returns true&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;# Other flags are untouched&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Let&#39;s clean up &lt;code&gt;original_enabled?&lt;/code&gt; at the end:&lt;/p&gt;
&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;metaclass&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;alias_method &lt;span class=&quot;token symbol&quot;&gt;:enabled?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;:original_enabled?&lt;/span&gt;
metaclass&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;undef_method &lt;span class=&quot;token symbol&quot;&gt;:original_enabled?&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Combining all of the above in a test helper gives us this monstrosity:&lt;/p&gt;
&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token function&quot;&gt;with_feature_enabled&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;block&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  metaclass &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Feature&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;instance_eval &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  metaclass&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;alias_method &lt;span class=&quot;token symbol&quot;&gt;:original_enabled?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;:enabled?&lt;/span&gt;

  stub &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Proc&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;arg&lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; arg&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;to_s &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; name&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;to_s
      &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt;
      Feature&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;original_enabled&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;arg&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;

  Feature&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;stub&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token symbol&quot;&gt;:enabled?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; stub&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;block&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;ensure&lt;/span&gt;
  metaclass&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;alias_method &lt;span class=&quot;token symbol&quot;&gt;:enabled?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;:original_enabled?&lt;/span&gt;
  metaclass&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;undef_method &lt;span class=&quot;token symbol&quot;&gt;:original_enabled?&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We could maybe have a simpler helper if we the &lt;code&gt;Feature&lt;/code&gt; class supported overriding flags. For example:&lt;/p&gt;
&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token function&quot;&gt;with_feature_enabled&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;name&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  Feature&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;overrides &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; name &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;yield&lt;/span&gt;
  Feature&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;overrides &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;However that would make the implementation of the &lt;code&gt;Feature&lt;/code&gt; class more complex. I consider that worse in this case, as this functionality would not be used outside of the test helper.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Unsurprisingly, the design that allows for dynamic toggling of flags is easier to use in tests.&lt;/p&gt;
&lt;p&gt;I generally find ways to write tests without stubs, but I don&#39;t see a good alternative in the application from which I took the example. Using a global configuration (be it read from the database or a file) for the feature flags is the most ergonomic for production use in this case.&lt;/p&gt;
&lt;p&gt;If there was a business need to override feature flags without deploying, I&#39;d revert to database storage or control via URL query parameters. Both of those would allow removing stubs from the tests.&lt;/p&gt;
&lt;p&gt;As it is, it&#39;s great that Ruby allows us to get our hands dirty when needed.&lt;/p&gt;
</content>
    
  </entry>
  
    
  
  <entry>
    <title>Broken video in Safari after DOM change</title>
    <link href="https://dimiterpetrov.com/blog/broken-video-in-safari-after-dom-change/"/>
    <updated>2020-06-22T00:00:00Z</updated>
    <id>https://dimiterpetrov.com/blog/broken-video-in-safari-after-dom-change/</id>
    
    <content type="html">&lt;p&gt;Let&#39;s say you&#39;re using &lt;a href=&quot;https://en.wikipedia.org/wiki/Ajax_(programming)&quot;&gt;early 2000s web tech&lt;/a&gt; to speed up page transitions, but with slightly newer APIs. When a link is clicked, the new page is loaded asynchronously (for example with &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API&quot;&gt;&lt;code&gt;fetch&lt;/code&gt;&lt;/a&gt;). Then a subsection of the current page is replaced by (a subsection of) the new page, using &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/ChildNode/replaceWith&quot;&gt;&lt;code&gt;replaceWith&lt;/code&gt;&lt;/a&gt; (or &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Node/replaceChild&quot;&gt;&lt;code&gt;replaceChild&lt;/code&gt;&lt;/a&gt; or a similar method depending on the implementation. There are many out there).&lt;/p&gt;
&lt;p&gt;It looks like Safari has some trouble when that new page contains a video element.&lt;/p&gt;
&lt;p&gt;I encountered this bug with a video element which defines a poster image, enables native controls and asks the browser to preload the metadata.&lt;/p&gt;
&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;video&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;preload&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;metadata&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;poster&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;thumbnail.jpeg&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;controls&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;source&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;src&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;video.mp4&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;video&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Safari displays the poster image after navigating to the new page, but it does not show the video controls. A quick look at the developer tools reveals that it also does not make any network requests to preload the metadata. On the other hand it&#39;s still possible to programmatically load and play the video from the developer tools console.&lt;/p&gt;
&lt;p&gt;Firefox and Chrome don&#39;t have this problem. Also, everything works normally after reloading the page in Safari, but that defeats the purpose of the speed optimization.&lt;/p&gt;
&lt;p&gt;It looks like this is related to how the video element is inserted into the page. The bug occurs with &lt;code&gt;replaceWith&lt;/code&gt;, but also with &lt;code&gt;replaceChild&lt;/code&gt; and re-implementations with &lt;code&gt;insertAjacentHTML&lt;/code&gt;, &lt;code&gt;insertBefore&lt;/code&gt; or &lt;code&gt;appendChild&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;I couldn&#39;t find any information about this online except a &lt;a href=&quot;http://blog.millermedeiros.com/unsolved-html5-video-issues-on-ios/#comment-48045&quot;&gt;passing comment from 2012&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;What I did find out after experimenting some more is that the unsafe &lt;code&gt;innerHTML&lt;/code&gt; (or its companion &lt;code&gt;outerHTML&lt;/code&gt;) fixes the bug.&lt;/p&gt;
&lt;p&gt;I would not replace the entire page with &lt;code&gt;innerHTML&lt;/code&gt;. However the part with the video contains no user input. My workaround is to continue using &lt;code&gt;replaceWith&lt;/code&gt;, but then refresh only the video elements:&lt;/p&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;querySelectorAll&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;video&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;forEach&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;video&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; content &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; video&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;outerHTML&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  video&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;outerHTML &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; content&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This removes event handlers from the &amp;quot;reset&amp;quot; video element, so I also needed to re-attach those.&lt;/p&gt;
</content>
    
  </entry>
  
    
  
  <entry>
    <title>Github OAuth authorization with serverless functions</title>
    <link href="https://dimiterpetrov.com/blog/github-oauth-authorization-with-serverless-functions/"/>
    <updated>2019-12-13T00:00:00Z</updated>
    <id>https://dimiterpetrov.com/blog/github-oauth-authorization-with-serverless-functions/</id>
    
    <content type="html">&lt;p&gt;At work I take care of many aspects of an application&#39;s life cycle: provisioning servers and configuring monitoring, setting up and verifying backups, automating certificate renewal, developing and maintaining the application, etc. A lot of it is a consequence of client requirements about the location of their data. Still, I&#39;m always on the lookout for ways to eliminate most of this work for projects with no hosting restrictions.&lt;/p&gt;
&lt;p&gt;One case that&#39;s been bothering me is our company&#39;s website, which is powered by a custom-built content management system. While hosting it on Heroku alleviates some of the burden, we still need to keep up with language and library versions. Thus, I&#39;ve been thinking about alternatives.&lt;/p&gt;
&lt;p&gt;The reason for the content management system is to allow more than developers to update the website. Any replacement would have to maintain this property.&lt;/p&gt;
&lt;p&gt;While exploring alternatives I experimented with the combination of the &lt;a href=&quot;https://www.11ty.io/&quot;&gt;Eleventy static site generator&lt;/a&gt;, &lt;a href=&quot;https://www.netlifycms.org/&quot;&gt;Netlify CMS&lt;/a&gt; for editing and &lt;a href=&quot;https://zeit.co/&quot;&gt;ZEIT Now&lt;/a&gt; for hosting.&lt;/p&gt;
&lt;p&gt;Eleventy seems to offer a lot of flexibility without needing much configuration. Netlify CMS provides a user-friendly interface for editing content, but still integrates with git. Finally, ZEIT Now automatically issues certificates and deploys every commit.&lt;/p&gt;
&lt;p&gt;Netlify CMS integrates nicely with the Netlify service and I would use that in production. However, I learned a more by &lt;em&gt;not&lt;/em&gt; hosting on Netlify in my experiments.&lt;/p&gt;
&lt;h2&gt;Netlify CMS authentication backends&lt;/h2&gt;
&lt;p&gt;Netlify CMS requires read access to the repository to present the current content to editors and write access to save changes. This is solved with OAuth. A user logs in with Github and the access token is transmitted to Netlify CMS which can then commit on the user&#39;s behalf.&lt;/p&gt;
&lt;p&gt;Github implements &lt;a href=&quot;https://tools.ietf.org/html/rfc6749#section-1.3.1&quot;&gt;OAuth2 with an authorization code grant&lt;/a&gt;. The Netlify CMS is entirely a client side solution and cannot obtain the access token directly with this type of grant. Normally it relies on Netlify to facilitate authentication, but also supports a &lt;a href=&quot;https://www.netlifycms.org/docs/authentication-backends/&quot;&gt;custom backend&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I read through the &lt;a href=&quot;https://www.netlifycms.org/docs/authentication-backends/#external-oauth-clients&quot;&gt;existing implementations&lt;/a&gt;, but did not like them. None of them have tests and many are susceptible to cross-site forgery attacks, so I decided to write my own backend (famous last words!).&lt;/p&gt;
&lt;h2&gt;&amp;quot;Serverless functions&amp;quot;&lt;/h2&gt;
&lt;p&gt;I realized I could host the authentication code directly on ZEIT Now using their &lt;a href=&quot;https://zeit.co/docs/v2/serverless-functions/introduction/&quot;&gt;&amp;quot;serverless functions&amp;quot;&lt;/a&gt;. The concept is that if your project contains an &lt;code&gt;api/&lt;/code&gt; directory with one request handler per file, Now deploys the functions alongside your static content.&lt;/p&gt;
&lt;p&gt;Up until that point I had not encountered a good use case for &amp;quot;serverless&amp;quot;. An OAuth 2.0 client workflow seemed like one. It suddenly clicked.&lt;/p&gt;
&lt;h2&gt;Requesting an authorization code from Github&lt;/h2&gt;
&lt;p&gt;The login flow starts with a redirect to the Github authorization screen. This screen informs about the requested permissions defined by the &lt;code&gt;scope&lt;/code&gt; parameter.&lt;/p&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; URLSearchParams &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;url&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; sign &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;jsonwebtoken&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; uuidv4 &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;uuid/v4&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

module&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function-variable function&quot;&gt;exports&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;req&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; res&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; params &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;client_id&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; process&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;env&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;GITHUB_CLIENT_ID&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;scope&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;repo&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token literal-property property&quot;&gt;state&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;sign&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;nonce&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;uuidv4&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      process&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;env&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;JWT_SECRET&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;expiresIn&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;30&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  res&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;writeHead&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;307&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token string-property property&quot;&gt;&#39;Location&#39;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;https://github.com/login/oauth/authorize?&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;URLSearchParams&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;params&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  res&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;end&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;a href=&quot;https://auth0.com/docs/protocols/oauth2/oauth-state&quot;&gt;&lt;code&gt;state&lt;/code&gt; parameter&lt;/a&gt; is the one that helps us to protect against &lt;a href=&quot;https://tools.ietf.org/html/rfc6749#section-10.12&quot;&gt;cross-site forgery attacks&lt;/a&gt;. In the request phase you set its value to something unique that cannot be guessed by an attacker. In the callback phase (see below), you check the state returned by the provider matches the one you sent.&lt;/p&gt;
&lt;p&gt;Here, the unique part is the UUID. It is transported as a &lt;a href=&quot;https://jwt.io/&quot;&gt;JSON web token&lt;/a&gt; signed with a symmetric secret key. Additionally the token expires after 30 seconds. That may be a bit short for the very first login as part of it requires human interaction: authorizing Netlify CMS to write to the repo. After that first time the process is fully automated and I expect Github to respond in less than 30 seconds.&lt;/p&gt;
&lt;h2&gt;Exchanging the authorization code a for an access token&lt;/h2&gt;
&lt;p&gt;If the user grants the rights to the app, they get redirected back to a predefined endpoint.&lt;/p&gt;
&lt;p&gt;There are two query parameters to check before going forward:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;code&lt;/code&gt;: this is the authorization code. If there is none, someone may be trying to bypass the Github identity check.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;state&lt;/code&gt;: this is the JSON web token sent in the first phase. Again, if that is invalid someone is trying to forge the request.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Assuming the parameters are correct, the authorization code can be exchanged for an access token.&lt;/p&gt;
&lt;p&gt;The described callback handler could look like this:&lt;/p&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; url &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;url&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; URLSearchParams &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; url&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; verify &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;jsonwebtoken&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; requestToken &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;../lib/request-token&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; renderSuccess&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; renderError &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;../lib/netlify-cms-login&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

module&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function-variable function&quot;&gt;exports&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;req&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; res&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; queryParams &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;URLSearchParams&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;parse&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;req&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;query&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; code &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; queryParams&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;code&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; state &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; queryParams&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;state&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; origin &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; process&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;env&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;ORIGIN&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;code&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    res&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;writeHead&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;400&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token string-property property&quot;&gt;&#39;Content-Type&#39;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;text/html; charset=utf-8&#39;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    res&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;end&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;renderError&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;origin&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;Code parameter missing&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;token function&quot;&gt;verify&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;state&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; process&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;env&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;JWT_SECRET&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;error&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; _&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;error&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      res&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;writeHead&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;400&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token string-property property&quot;&gt;&#39;Content-Type&#39;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;text/html; charset=utf-8&#39;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      res&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;end&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;renderError&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;origin&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;Invalid state parameter&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; tokenResponse &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;requestToken&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;code&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; access_token&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; error&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; error_description &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; tokenResponse&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;error&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    res&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;writeHead&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;400&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token string-property property&quot;&gt;&#39;Content-Type&#39;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;text/html; charset=utf-8&#39;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    res&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;end&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;renderError&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;origin&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; error&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

  res&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;writeHead&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;200&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token string-property property&quot;&gt;&#39;Content-Type&#39;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;text/html; charset=utf-8&#39;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  res&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;end&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;renderSuccess&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;origin&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; access_token&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;renderSuccess&lt;/code&gt; and &lt;code&gt;renderError&lt;/code&gt; functions return responses that can be handled by Netlify CMS.&lt;/p&gt;
&lt;h2&gt;The Netlify CMS login handshake&lt;/h2&gt;
&lt;p&gt;I haven&#39;t found documentation about this part. It seems like everybody reads the Netlify CMS source code to understand how it works.&lt;/p&gt;
&lt;p&gt;When you press the login button, Netlify CMS open the aforementioned login entry point in a new window. Then it stars listening for messages from that window. When that pop-up window has obtained an access token, it is expected to transmit it back to its parent window.&lt;/p&gt;
&lt;p&gt;In the case of the Github workflow, the communication goes something like this:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The pop-up window sends the message &lt;code&gt;authorizing:github&lt;/code&gt; to the window that spawned it.&lt;/li&gt;
&lt;li&gt;The original window (where Netlify CMS is running) sends back something to the effect of &amp;quot;Oh yeah? Do you have an access token?&amp;quot; (actually it echoes back the same message as far as I can tell)&lt;/li&gt;
&lt;li&gt;The pop-up relays Github&#39;s response in of these two forms:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;// error case
authorization:github:error:{&amp;quot;message&amp;quot;: &amp;quot;The error message&amp;quot;}

// success case
authorization:github:success:{&amp;quot;token&amp;quot;:&amp;quot;access token value&amp;quot;}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The communication occurs via &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage&quot;&gt;&lt;code&gt;window.postMessage&lt;/code&gt;&lt;/a&gt;. It&#39;s crucial to verify who is the sender of such a message before acting on its contents.&lt;/p&gt;
&lt;p&gt;With that in mind here is an example of what &lt;code&gt;renderSuccess&lt;/code&gt; from the previous section might return:&lt;/p&gt;
&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;token doctype&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;!&lt;/span&gt;&lt;span class=&quot;token doctype-tag&quot;&gt;doctype&lt;/span&gt; &lt;span class=&quot;token name&quot;&gt;html&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;body&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;script&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token script&quot;&gt;&lt;span class=&quot;token language-javascript&quot;&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;recieveMessage&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;e&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;origin &lt;span class=&quot;token operator&quot;&gt;!==&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;https://app.domain:1234&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
      window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;opener&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;postMessage&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;token string&quot;&gt;&#39;authorization:github:success:{&quot;token&quot;:&quot;sometoken&quot;}&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        e&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;origin
      &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;message&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; recieveMessage&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;opener&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;postMessage&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
      &lt;span class=&quot;token string&quot;&gt;&quot;authorizing:github&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token string&quot;&gt;&quot;https://app.domain:1234&quot;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;script&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;body&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;html&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Testing&lt;/h2&gt;
&lt;p&gt;As seen above, ZEIT works with regular Node.js request handlers. One way to test a handler is to start an HTTP server, install that handler and send requests to it.&lt;/p&gt;
&lt;p&gt;Here is for example the test for the &lt;code&gt;/api/auth&lt;/code&gt; endpoint.&lt;/p&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; test &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;ava&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; http &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;http&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; listen &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;test-listen&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; fetch &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;node-fetch&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; verify &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;jsonwebtoken&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; URLSearchParams &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;url&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; auth &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;../api/auth&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;token function&quot;&gt;test&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;redirects to Github OAuth workflow start&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token parameter&quot;&gt;t&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; server &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; http&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;createServer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;auth&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; url &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;listen&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;server&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; env &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; process&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;env&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  process&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;env&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;GITHUB_CLIENT_ID&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;dummy-id&#39;&lt;/span&gt;
  process&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;env&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;JWT_SECRET&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;jwt-secret&#39;&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; response &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;fetch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;url&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;redirect&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;manual&#39;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;let&lt;/span&gt; location &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; response&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;headers&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;Location&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  t&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;is&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;response&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;status&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;307&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  t&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;location&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;startsWith&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;https://github.com/login/oauth/authorize?client_id=dummy-id&amp;amp;scope=repo&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;verify&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;URLSearchParams&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;location&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;state&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;jwt-secret&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;error&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; _&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;error&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      t&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;fail&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;Expected a valid JSON web token to be sent as state, got &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;${&lt;/span&gt;error&lt;span class=&quot;token interpolation-punctuation punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

  process&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;env &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; env&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  server&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;close&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The request handler reads the secrets from the process environment. That is why I set some environment variables at the start and restore the environment after the test.&lt;/p&gt;
&lt;p&gt;The &lt;a href=&quot;https://github.com/zeit/test-listen&quot;&gt;test-listen&lt;/a&gt; library makes the HTTP server listen on an arbitrary unused port and returns the complete URL. That way all tests can run in parallel without port collisions.&lt;/p&gt;
&lt;h2&gt;Example source code&lt;/h2&gt;
&lt;p&gt;Read the &lt;a href=&quot;https://github.com/crackofdusk/eleventy-sample/&quot;&gt;complete source code for this experiment&lt;/a&gt; for more details about integrating with the Netlify CMS.&lt;/p&gt;
</content>
    
  </entry>
  
    
  
  <entry>
    <title>A tale of flaky Cypress tests</title>
    <link href="https://dimiterpetrov.com/blog/a-tale-of-flaky-cypress-tests/"/>
    <updated>2019-10-24T00:00:00Z</updated>
    <id>https://dimiterpetrov.com/blog/a-tale-of-flaky-cypress-tests/</id>
    
    <content type="html">&lt;p&gt;I wrote some flaky frontend tests using Cypress and then learned how to make them deterministic. This is the tale of that adventure.&lt;/p&gt;
&lt;p&gt;An application I am working has a video player that should continue playing when changing pages. Additionally the player should be visible on the dedicated page of the video it is playing and hidden on other pages.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;&amp;lt;video&amp;gt;&lt;/code&gt; element cannot move within the DOM, because it would be destroyed and recreated, which interrupts the playback. The trick is to only change part of the page. That particular application achieves that with &lt;a href=&quot;https://github.com/defunkt/jquery-pjax&quot;&gt;pjax&lt;/a&gt; (which is also still used by Github at the time of writing -- search for &amp;quot;pjax&amp;quot; in their HTML responses). The player lies outside the part that gets swapped during navigation. It is placed in a predefined area using CSS positioning.&lt;/p&gt;
&lt;p&gt;I wanted to replace a lot of the player code, but there was no test coverage of the aforementioned requirements. I decided to add some using Cypress. I ended up with a long-ish integration test that does the following:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Visit a page where the player should be displayed and press play&lt;/li&gt;
&lt;li&gt;Assert the player is covering its dedicated placeholder&lt;/li&gt;
&lt;li&gt;Navigate to a another page and assert the player is hidden&lt;/li&gt;
&lt;li&gt;Navigate to a page with a placeholder/preview of a different video and assert the player remains hidden&lt;/li&gt;
&lt;li&gt;Go back to the page of the currently playing media and assert the player is placed correctly again&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The first assertions was already causing me problems as a newcomer to Cypress.&lt;/p&gt;
&lt;h2&gt;Scroll, scroll, scroll&lt;/h2&gt;
&lt;p&gt;Cypress scrolls an element into view before clicking. The &lt;a href=&quot;https://docs.cypress.io/guides/core-concepts/interacting-with-elements.html#Scrolling&quot;&gt;scrolling algorithm&lt;/a&gt; puts the element at the top of the page.&lt;/p&gt;
&lt;p&gt;If the top of the page is covered by a floating header that always remains there, you will need to disable the checks before the click:&lt;/p&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;cy&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;selector&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;click&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token literal-property property&quot;&gt;force&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Working directly with a DOM element&lt;/h2&gt;
&lt;p&gt;The test clicks the play button and some music plays. I don&#39;t want the hear the music when running the test suite, so I tried to mute or pause it.&lt;/p&gt;
&lt;p&gt;Usually, you would call a function with &lt;code&gt;invoke&lt;/code&gt;. This does not work though:&lt;/p&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;cy&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;[data-cy=player]&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;should&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;be.visible&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;invoke&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;pause&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It fails with the following error:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;cy.invoke() waited for the specified property &#39;pause&#39; to exist, but it never did.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;That is because &lt;code&gt;get()&lt;/code&gt; returns a list of elements even if there&#39;s only one.&lt;/p&gt;
&lt;p&gt;My first instinct to unwrap it also did not work:&lt;/p&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;// Results in error&lt;/span&gt;
cy&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;@player&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;invoke&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;get&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;invoke&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;pause&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This did:&lt;/p&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;cy&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;@player&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;element&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; element&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;pause&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Flaky behaviour&lt;/h2&gt;
&lt;p&gt;I then went on to test that the player covers its placeholder:&lt;/p&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;cy&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;[data-cy=player]&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;player&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  cy&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;[data-cy=player-placeholder]&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;placeholder&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    cy&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;wrap&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;player&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;offset&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;should&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;deep.eq&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; placeholder&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;offset&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    cy&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;wrap&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;player&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;width&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;should&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;eq&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; placeholder&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;width&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    cy&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;wrap&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;player&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;height&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;should&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;eq&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; placeholder&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;height&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That worked &lt;em&gt;sometimes&lt;/em&gt; in the Cypress UI and never in headless mode. Because the player is only displayed once it starts playing, sometimes Cypress would check its position before it has moved.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;CypressError: Timed out retrying: expected { top: -10000, left: 0 } to deeply equal { top: 138, left: 0 }&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Cypress deals with race conditions by &lt;a href=&quot;https://docs.cypress.io/guides/core-concepts/retry-ability.html&quot;&gt;automatically retrying commands and assertions&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;It does not retry &lt;em&gt;every&lt;/em&gt; command, though, and I spent a long time searching for a way to retry getting the player position and dimensions. Some of it was reading the Cypress source code. One attempt even resulted in an infinite loop that kept retrieving the same DOM element!&lt;/p&gt;
&lt;p&gt;I eventually found &lt;a href=&quot;https://github.com/cypress-io/cypress/issues/3109#issuecomment-468409425&quot;&gt;a comment&lt;/a&gt; describing a hack using the public API.&lt;/p&gt;
&lt;h2&gt;Make any function call retry-able&lt;/h2&gt;
&lt;p&gt;The trick involves a custom command.&lt;/p&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;try&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;subject&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; fn&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; cy&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;wrap&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token function-variable function&quot;&gt;try&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;fn&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;subject&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;invoke&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;try&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;(&lt;code&gt;log&lt;/code&gt; is set to &lt;code&gt;false&lt;/code&gt; to exclude these implementation details from the command trace in the Cypress log)&lt;/p&gt;
&lt;p&gt;Finally, I had a reliable test.&lt;/p&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;cy&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;[data-cy=player-placeholder]&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;placeholder&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  cy&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;@player&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;try&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;boundingBox&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;should&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;box&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    assert&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;deepEqual&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;box&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;boundingBox&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;placeholder&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token string&quot;&gt;&#39;the player is covering its placeholder during playback&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;boundingBox&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;element&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; element&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;getBoundingClientRect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When the assertion in the callback to &lt;code&gt;should()&lt;/code&gt; fails, Cypress retries the last command in the chain.&lt;/p&gt;
&lt;h2&gt;More flakiness&lt;/h2&gt;
&lt;p&gt;Funnily enough I had another race condition in the test that I did not understand until I started writing this post. The music did not pause during some test runs. The real test code that should pause it is as follows.&lt;/p&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;cy&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;[data-cy=player]&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;should&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;be.visible&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;container&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; container&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;find&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;audio&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;pause&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The culprit is the visibility assertion. The element is hidden by default using a large negative margin for historical reasons involving old Internet Explorer versions and a Flash fallback for the player. IE would completely disregard the Flash object if it had &lt;code&gt;visiblity: hidden&lt;/code&gt;. The workaround that tricked IE into believing the off-screen player was visible also has an effect on Cypress. There is nothing in &lt;a href=&quot;https://docs.cypress.io/guides/core-concepts/interacting-with-elements.html#Visibility&quot;&gt;Cypress&#39; definition of visibility&lt;/a&gt; saying an element which is ten thousand pixels above the viewport is hidden.&lt;/p&gt;
&lt;p&gt;My assumption was that a &amp;quot;visible&amp;quot; player is playing, because in the implementation we change its coordinates after starting playback. However for Cypress the element is always visible, so it immediately tries to pause which causes the race condition.&lt;/p&gt;
&lt;p&gt;I can think of two ways of fixing this. One is to mute the sound at the start of the test and let the audio/video play.&lt;/p&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;cy&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;[data-cy=player] audio&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;player&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; player&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;muted &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The other is to explicitly wait for the playback to start before pausing:&lt;/p&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;cy&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;[data-cy=player] audio&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;should&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;$element&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; player &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; $element&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    assert&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;isTrue&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;isPlaying&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;player&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;the media is playing&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    player&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;pause&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;// ...&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;isPlaying&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;media&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;media&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;duration &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt;
    &lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;media&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;paused &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt;
    &lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;media&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ended &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt;
    media&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;readyState &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;should()&lt;/code&gt; is retried if the assertion in the callback fails. Pausing the player after the assertion ensures it will be playing by that time. (The &lt;code&gt;isPlaying()&lt;/code&gt; helper is a bit awkward, because there is no &lt;code&gt;playing&lt;/code&gt; property on &lt;code&gt;HTMLMediaElement&lt;/code&gt;.)&lt;/p&gt;
&lt;p&gt;How can we make sure this works? By introducing a delay in the production code.&lt;/p&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Promise&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;resolve&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;setTimeout&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;resolve&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2000&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;then&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;play&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
</content>
    
  </entry>
  
    
  
  <entry>
    <title>Setting up Cypress with Rails</title>
    <link href="https://blog.simplificator.com/2019/10/11/setting-up-cypress-with-rails/"/>
    <updated>2019-10-11T00:00:00Z</updated>
    <id>https://blog.simplificator.com/2019/10/11/setting-up-cypress-with-rails/</id>
    
    <content type="html">Tips for integrating the Cypress testing framework into a Ruby on Rails application</content>
    
  </entry>
  
    
  
  <entry>
    <title>Decoding disparate JSON fields into a custom Elm type</title>
    <link href="https://dimiterpetrov.com/blog/decoding-disparate-json-fields-into-custom-elm-type/"/>
    <updated>2019-03-20T00:00:00Z</updated>
    <id>https://dimiterpetrov.com/blog/decoding-disparate-json-fields-into-custom-elm-type/</id>
    
    <content type="html">&lt;p&gt;Update (2019-11-06): I think what I describe is an example of what people call &amp;quot;&lt;a href=&quot;https://lexi-lambda.github.io/blog/2019/11/05/parse-don-t-validate/&quot;&gt;parse, don&#39;t validate&lt;/a&gt;&amp;quot;.&lt;/p&gt;
&lt;p&gt;I wrote a simple video player in Elm as an experiment. My earliest realization was that authoritative source for player state the HTML media element (&lt;code&gt;&amp;lt;audio&amp;gt;&lt;/code&gt; or &lt;code&gt;&amp;lt;video&amp;gt;&lt;/code&gt;) and not the Elm application. I decided to keep the Elm model in sync by sending the JSON-encoded &lt;a href=&quot;https://html.spec.whatwg.org/multipage/media.html#media-elements&quot;&gt;HTMLMediaElement&lt;/a&gt; over a port whenever any change in the media element occurs.&lt;/p&gt;
&lt;pre class=&quot;language-js&quot;&gt;&lt;code class=&quot;language-js&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;var&lt;/span&gt; events &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
  &lt;span class=&quot;token string&quot;&gt;&quot;abort&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token string&quot;&gt;&quot;canplay&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token string&quot;&gt;&quot;canplaythrough&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token string&quot;&gt;&quot;change&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token string&quot;&gt;&quot;error&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;/* ... and many more...  */&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;

events&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;forEach&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;eventName&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  media&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;eventName&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    app&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ports&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;inbound&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;send&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;target&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It&#39;s tempting to define a model on the receiving side that represents the JSON data structure as is. The fields would be as defined by the specification. It would be easy to look up documentation and trace the flow of values. For example, one could look at the Elm debugger and see that there is a &lt;code&gt;media.readyState&lt;/code&gt; value of &lt;code&gt;4&lt;/code&gt;, then look up what a &amp;quot;ready state&amp;quot; is and what &lt;code&gt;HAVE_ENOUGH_DATA&lt;/code&gt; (defined as &lt;code&gt;4&lt;/code&gt;) means.&lt;/p&gt;
&lt;p&gt;However, I don&#39;t like that:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;mutually exclusive states are represented as boolean attributes. For example, &lt;code&gt;ended&lt;/code&gt; and &lt;code&gt;paused&lt;/code&gt; will never both be true in practice, but the data structure allows for that combination.&lt;/li&gt;
&lt;li&gt;somewhat related, &lt;code&gt;error&lt;/code&gt; is &lt;code&gt;null&lt;/code&gt; when there are no errors and contains a &lt;code&gt;code&lt;/code&gt; and &lt;code&gt;message&lt;/code&gt; if there is an error.&lt;/li&gt;
&lt;li&gt;it&#39;s hard to tell whether the media is playing. There is a &lt;code&gt;playing&lt;/code&gt; event, but no &lt;code&gt;playing&lt;/code&gt; attribute, like there is for example a &lt;code&gt;seeking&lt;/code&gt; or &lt;code&gt;paused&lt;/code&gt; attribute.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I prefer the following data structure:&lt;/p&gt;
&lt;pre class=&quot;language-elm&quot;&gt;&lt;code class=&quot;language-elm&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;{-| Current status of media playback
-}&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;Playback&lt;/span&gt;
    &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;Playing&lt;/span&gt;
    &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;Paused&lt;/span&gt;
    &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;Ended&lt;/span&gt;
    &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;PlaybackError&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;Error&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;Error&lt;/span&gt;
    &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;UnsupportedSource&lt;/span&gt;
    &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;BrowserPolicyViolation&lt;/span&gt;
    &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;NetworkConnectivity&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It&#39;s missing some states I didn&#39;t need for my experiment, like &amp;quot;loading metadata&amp;quot; or &amp;quot;buffering&amp;quot;, but it&#39;s easy to add them.&lt;/p&gt;
&lt;p&gt;What&#39;s neat about this type is that the variants are mutually exclusive. It can avoid errors like trying to programmatically pause a video that has already ended. It can also make state machine transitions explicit, like &amp;quot;load a new video when this one ends playing&amp;quot;.&lt;/p&gt;
&lt;pre class=&quot;language-elm&quot;&gt;&lt;code class=&quot;language-elm&quot;&gt;&lt;span class=&quot;token hvariable&quot;&gt;handleMediaUpdate&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;Model&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;Media.State&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;Model&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;Cmd&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;Msg&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token hvariable&quot;&gt;handleMediaUpdate&lt;/span&gt; &lt;span class=&quot;token hvariable&quot;&gt;model&lt;/span&gt; &lt;span class=&quot;token hvariable&quot;&gt;media&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token hvariable&quot;&gt;media&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token hvariable&quot;&gt;playback&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;of&lt;/span&gt;
        &lt;span class=&quot;token constant&quot;&gt;Media.Ended&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;
            &lt;span class=&quot;token hvariable&quot;&gt;playNext&lt;/span&gt; &lt;span class=&quot;token hvariable&quot;&gt;model&lt;/span&gt;

        &lt;span class=&quot;token comment&quot;&gt;-- ...&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;How do we transform the JSON representation of the media element into this handy data structure?&lt;/p&gt;
&lt;p&gt;The JSON has this shape:&lt;/p&gt;
&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;autoplay&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;controls&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;currentTime&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;3.999659&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;ended&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;error&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token null keyword&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;paused&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;seeking&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;src&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;http://localhost:8000/test.wav&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;volume&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;// and many other fields...&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To build a &lt;code&gt;Playback&lt;/code&gt; value out of this, we need to decode &lt;em&gt;multiple&lt;/em&gt; fields, then combine them.&lt;/p&gt;
&lt;pre class=&quot;language-elm&quot;&gt;&lt;code class=&quot;language-elm&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;{-| State decoder for HTMLMediaElement objects
-}&lt;/span&gt;
&lt;span class=&quot;token hvariable&quot;&gt;decoder&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;Json.Decode.Decoder&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;Playback&lt;/span&gt;
&lt;span class=&quot;token hvariable&quot;&gt;decoder&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;
    &lt;span class=&quot;token hvariable&quot;&gt;Json.Decode.map3&lt;/span&gt; &lt;span class=&quot;token hvariable&quot;&gt;toPlayback&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token hvariable&quot;&gt;Json.Decode.field&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;paused&quot;&lt;/span&gt; &lt;span class=&quot;token hvariable&quot;&gt;Json.Decode.bool&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token hvariable&quot;&gt;Json.Decode.field&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;ended&quot;&lt;/span&gt; &lt;span class=&quot;token hvariable&quot;&gt;Json.Decode.bool&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token hvariable&quot;&gt;Json.Decode.field&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;error&quot;&lt;/span&gt; &lt;span class=&quot;token hvariable&quot;&gt;errorDecoder&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The trick is in the &lt;code&gt;toPlayback&lt;/code&gt; function:&lt;/p&gt;
&lt;pre class=&quot;language-elm&quot;&gt;&lt;code class=&quot;language-elm&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;{-| A helper function to decode related fields into a custom type. Be careful
with the order of arguments, there is potential for confusing the two booleans
-}&lt;/span&gt;
&lt;span class=&quot;token hvariable&quot;&gt;toPlayback&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;Bool&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;Bool&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;Maybe&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;Error&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;Playback&lt;/span&gt;
&lt;span class=&quot;token hvariable&quot;&gt;toPlayback&lt;/span&gt; &lt;span class=&quot;token hvariable&quot;&gt;paused&lt;/span&gt; &lt;span class=&quot;token hvariable&quot;&gt;ended&lt;/span&gt; &lt;span class=&quot;token hvariable&quot;&gt;maybeError&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;token hvariable&quot;&gt;paused&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token hvariable&quot;&gt;ended&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token hvariable&quot;&gt;maybeError&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;of&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt; _&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; _&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;Just&lt;/span&gt; &lt;span class=&quot;token hvariable&quot;&gt;error&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;
            &lt;span class=&quot;token constant&quot;&gt;PlaybackError&lt;/span&gt; &lt;span class=&quot;token hvariable&quot;&gt;error&lt;/span&gt;

        &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt; _&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; _ &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;
            &lt;span class=&quot;token constant&quot;&gt;Ended&lt;/span&gt;

        &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;True&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; _&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; _ &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;
            &lt;span class=&quot;token constant&quot;&gt;Paused&lt;/span&gt;

        _ &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;
            &lt;span class=&quot;token comment&quot;&gt;-- FIXME: it&#39;s only really playing if readyState is 3 or 4&lt;/span&gt;
            &lt;span class=&quot;token constant&quot;&gt;Playing&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;(The catch-all branch is an oversimplification. As far as I know, we also need to take into account the &lt;a href=&quot;https://html.spec.whatwg.org/multipage/media.html#ready-states&quot;&gt;readyState&lt;/a&gt; to derive whether the media is playing. I took a shortcut in the initial implementation.)&lt;/p&gt;
&lt;p&gt;With this, the complexity is handled at the system &lt;a href=&quot;https://www.destroyallsoftware.com/talks/boundaries&quot;&gt;boundary&lt;/a&gt;. It is the only spot that deals with booleans. It&#39;s also the only place in the Elm code which creates a &lt;code&gt;Playback&lt;/code&gt; value. I would know where to look if there was a bug in the playback state.&lt;/p&gt;
</content>
    
  </entry>
  
    
  
  <entry>
    <title>Long-lived background Ruby processes</title>
    <link href="https://dimiterpetrov.com/blog/long-lived-background-ruby-processes/"/>
    <updated>2018-10-28T00:00:00Z</updated>
    <id>https://dimiterpetrov.com/blog/long-lived-background-ruby-processes/</id>
    
    <content type="html">&lt;p&gt;I needed a way to run a process alongside a Rails application for an always-running live data ingestion task. Sidekiq is only meant for short jobs, but Sidekiq itself is a long-lived process. That gave me the idea to look at the source code of Sidekiq, Resque and Puma for inspiration.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Update:&lt;/em&gt; In the meantime, Sidekiq itself has &lt;a href=&quot;https://www.mikeperham.com/2019/09/03/welcome-to-sidekiq-6.0/&quot;&gt;removed this capability&lt;/a&gt; and relies on a supervising process like systemd.&lt;/p&gt;
&lt;p&gt;In order to have a long-running process, we need:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A way to daemonize the process&lt;/li&gt;
&lt;li&gt;A logger, so that we know what&#39;s going on&lt;/li&gt;
&lt;li&gt;A PID file, so that we can stop the process&lt;/li&gt;
&lt;li&gt;A signal handler to handle shutdowns gracefully (look at Sidekiq for a more flexible take on this)&lt;/li&gt;
&lt;li&gt;A Rake task to easily launch the process with a Rails context&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Start&lt;/h2&gt;
&lt;p&gt;The easiest way to start the daemon is with a Rake task:&lt;/p&gt;
&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;task ingest&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;:environment&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;do&lt;/span&gt;
  &lt;span class=&quot;token class-name&quot;&gt;Ingestion&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;token symbol&quot;&gt;logfile&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Rails&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;root&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;join&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&#39;log&#39;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&#39;ingestion.log&#39;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token symbol&quot;&gt;pidfile&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Rails&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;root&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;join&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&#39;tmp&#39;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&#39;pids&#39;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&#39;ingestion.pid&#39;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ingest
&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The dependency to &lt;code&gt;environment&lt;/code&gt; is essential. It&#39;s what makes sure Rails is loaded.&lt;/p&gt;
&lt;p&gt;The tasks delegates to the &lt;code&gt;Ingestion&lt;/code&gt; class, which orchestrates the work:&lt;/p&gt;
&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token function&quot;&gt;ingest&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token class-name&quot;&gt;IngestionDaemon&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token symbol&quot;&gt;logger&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; logger&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;pidfile&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; pidfile&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;work &lt;span class=&quot;token keyword&quot;&gt;do&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;# ingest data&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Shutdown&lt;/h2&gt;
&lt;p&gt;Since we want to keep track of the background process, we write its ID to a file.&lt;/p&gt;
&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;&lt;span class=&quot;token builtin&quot;&gt;File&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;open&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;pidfile&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&#39;w&#39;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;f&lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; f &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; Process&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;pid &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
at_exit &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; FileUtils&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;rm_f pidfile &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The pidfile is cleaned up just before the program exits&lt;/p&gt;
&lt;p&gt;We can terminate the daemon with the &lt;code&gt;kill&lt;/code&gt; command:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;kill $(cat tmp/pids/ingestion.pid)
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Signal handling&lt;/h2&gt;
&lt;p&gt;We can politely ask the program to stop by sending it a signal.&lt;/p&gt;
&lt;p&gt;Before putting the program in the background, we define which signals to handle and how using &lt;code&gt;Signal.trap&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;trap&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&#39;INT&#39;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; interrupt &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
trap&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&#39;TERM&#39;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; interrupt &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;interrupt&lt;/code&gt; method above raises &lt;code&gt;Interrupt&lt;/code&gt; which is also the exception raised by Ruby when you press Control-C to stop a program. Here we explicitly handle this case, but also the termination signal. That one is sent by default by the &lt;code&gt;kill&lt;/code&gt; command (see the &lt;code&gt;kill(1)&lt;/code&gt; and &lt;code&gt;signal(7)&lt;/code&gt; manual pages).&lt;/p&gt;
&lt;p&gt;Then it&#39;s a matter to rescuing the &lt;code&gt;Interrupt&lt;/code&gt; exception at the appropriate place in the program and cleaning up.&lt;/p&gt;
&lt;p&gt;Read up on the &lt;a href=&quot;https://www.sitepoint.com/the-self-pipe-trick-explained/&quot;&gt;self-pipe trick&lt;/a&gt; for more robust signal handling. I did not need it for this program.&lt;/p&gt;
&lt;h2&gt;Detach the process from controlling terminal&lt;/h2&gt;
&lt;p&gt;The Ruby API for this is a bit confusing:&lt;/p&gt;
&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;Process&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;daemon&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The first argument controls whether or not to change the current working directory to root (&lt;code&gt;/&lt;/code&gt;). &lt;code&gt;true&lt;/code&gt; tells Ruby to keep the current directory. The second argument controls whether to keep the input and output streams or redirect them to &lt;code&gt;/dev/null&lt;/code&gt;. &lt;code&gt;false&lt;/code&gt; tells Ruby explicitly to redirect to &lt;code&gt;/dev/null&lt;/code&gt;. If you don&#39;t &amp;quot;close&amp;quot; the streams, the process will continue writing to the terminal even after it&#39;s detached.&lt;/p&gt;
&lt;h2&gt;Wrap-up&lt;/h2&gt;
&lt;p&gt;Here is the entire class:&lt;/p&gt;
&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;IngestionDaemon&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token function&quot;&gt;initialize&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token symbol&quot;&gt;logger&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;pidfile&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token variable&quot;&gt;@logger&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; logger
    &lt;span class=&quot;token variable&quot;&gt;@pidfile&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; pidfile
  &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token function&quot;&gt;work&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;block&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    register_signal_handlers
    daemonize
    write_pid
    ingest&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;block&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;rescue&lt;/span&gt; Interrupt
    shutdown
  &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt;

  attr_reader &lt;span class=&quot;token symbol&quot;&gt;:logger&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;:pidfile&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token function&quot;&gt;register_signal_handlers&lt;/span&gt;&lt;/span&gt;
    trap&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&#39;INT&#39;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; interrupt &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    trap&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&#39;TERM&#39;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; interrupt &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token function&quot;&gt;daemonize&lt;/span&gt;&lt;/span&gt;
    Process&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;daemon&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token function&quot;&gt;write_pid&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token builtin&quot;&gt;File&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;open&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;pidfile&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&#39;w&#39;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;f&lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; f &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; Process&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;pid &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    at_exit &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; delete_pidfile &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token function&quot;&gt;delete_pidfile&lt;/span&gt;&lt;/span&gt;
    FileUtils&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;rm_f pidfile
  &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token function&quot;&gt;ingest&lt;/span&gt;&lt;/span&gt;
    logger&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;info &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&#39;Starting ingestion daemon&#39;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;yield&lt;/span&gt;
    logger&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;info &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&#39;Ingestion daemon ran out of work&#39;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token function&quot;&gt;interrupt&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;raise&lt;/span&gt; Interrupt
  &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token function&quot;&gt;shutdown&lt;/span&gt;&lt;/span&gt;
    logger&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;info &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&#39;Shutting down ingestion daemon&#39;&lt;/span&gt;&lt;/span&gt;
    exit&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
</content>
    
  </entry>
  
    
  
  <entry>
    <title>Keeping Elm JSON decoders in sync with your Rails API</title>
    <link href="https://dimiterpetrov.com/blog/keeping-elm-json-decoders-in-sync-with-your-rails-api/"/>
    <updated>2017-09-11T00:00:00Z</updated>
    <id>https://dimiterpetrov.com/blog/keeping-elm-json-decoders-in-sync-with-your-rails-api/</id>
    
    <content type="html">&lt;p&gt;Implementing and consuming APIs is very error-prone. I&#39;ve now worked on multiple single page applications written in Elm and I&#39;ve grown to love the constraint of having to explicitly define JSON decoders. They are very helpful in exposing bugs and errors in documentation.&lt;/p&gt;
&lt;p&gt;In this post, I describe a strategy I&#39;ve adopted to catch compatibility mistakes as early as possible. It involves &lt;a href=&quot;http://json-schema.org/&quot;&gt;JSON Schema&lt;/a&gt; in automated tests for both the backend and frontend. JSON Schema is not a silver bullet, but it has been useful in my personal experience and is worth the trouble to gain some extra confidence in a program.&lt;/p&gt;
&lt;p&gt;The main ideas behind the approach are:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;the API producer and consumers should always comply with the same schema version&lt;/li&gt;
&lt;li&gt;maintaining and sharing the schema and validating against the schema should be very convenient, even automated if possible, otherwise you will stop doing it.&lt;/li&gt;
&lt;li&gt;tests should fail when either side breaks the contract&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This post uses Elm 0.18 and Ruby on Rails, but it may be relevant to future Elm versions and certainly applies to other backend programming languages and ecosystems.&lt;/p&gt;
&lt;h2&gt;Describing JSON schemas in Elm&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;http://package.elm-lang.org/packages/NoRedInk/json-elm-schema/latest&quot;&gt;json-elm-schema&lt;/a&gt; is an Elm library for defining JSON schemas. For example:&lt;/p&gt;
&lt;pre class=&quot;language-elm&quot;&gt;&lt;code class=&quot;language-elm&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;module&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;Schema.Profile&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;exposing&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token hvariable&quot;&gt;schema&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token import-statement&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; Json.Encode &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; Encode&lt;/span&gt;
&lt;span class=&quot;token import-statement&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; JsonSchema &lt;span class=&quot;token keyword&quot;&gt;exposing&lt;/span&gt; &lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;


&lt;span class=&quot;token hvariable&quot;&gt;schema&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;Schema&lt;/span&gt;
&lt;span class=&quot;token hvariable&quot;&gt;schema&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;
    &lt;span class=&quot;token hvariable&quot;&gt;object&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;token hvariable&quot;&gt;title&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;User profile&quot;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token hvariable&quot;&gt;properties&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;token hvariable&quot;&gt;required&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;name&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;|&lt;/span&gt; &lt;span class=&quot;token hvariable&quot;&gt;string&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token hvariable&quot;&gt;optional&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;age&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;|&lt;/span&gt;
                &lt;span class=&quot;token hvariable&quot;&gt;integer&lt;/span&gt;
                    &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;token hvariable&quot;&gt;description&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Age in years&quot;&lt;/span&gt;
                    &lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token hvariable&quot;&gt;minimum&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;
                    &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token hvariable&quot;&gt;examples&lt;/span&gt; &lt;span class=&quot;token hvariable&quot;&gt;Encode.object&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token hvariable&quot;&gt;Encode.string&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Jane Doe&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
              &lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;age&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token hvariable&quot;&gt;Encode.int&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;42&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
              &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token hvariable&quot;&gt;Encode.string&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;John Smith&quot;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
              &lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;age&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token hvariable&quot;&gt;Encode.int&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;25&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
              &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This package helps:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;prevent schema authoring mistakes&lt;/li&gt;
&lt;li&gt;validate JSON structures against a schema&lt;/li&gt;
&lt;li&gt;generate JSON structures that correspond to a schema&lt;/li&gt;
&lt;li&gt;convert (in both directions) between a schema written in Elm like above and a normal JSON Schema&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It is going to be the foundation of the entire approach.&lt;/p&gt;
&lt;h2&gt;Easy fuzz tests for JSON decoders&lt;/h2&gt;
&lt;p&gt;Say we have a function allowing us to decode the API response described above into an Elm record.&lt;/p&gt;
&lt;pre class=&quot;language-elm&quot;&gt;&lt;code class=&quot;language-elm&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;module&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;Profile&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;exposing&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;Profile&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token hvariable&quot;&gt;decoder&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token import-statement&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; Json.Decode &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; Decode &lt;span class=&quot;token keyword&quot;&gt;exposing&lt;/span&gt; &lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;Decoder&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;


&lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;alias&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;Profile&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token hvariable&quot;&gt;name&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;String&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token hvariable&quot;&gt;age&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;Int&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;


&lt;span class=&quot;token hvariable&quot;&gt;decoder&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;Decoder&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;Profile&lt;/span&gt;
&lt;span class=&quot;token hvariable&quot;&gt;decoder&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;
    &lt;span class=&quot;token hvariable&quot;&gt;Decode.map2&lt;/span&gt;
        &lt;span class=&quot;token constant&quot;&gt;Profile&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token hvariable&quot;&gt;Decode.field&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;name&quot;&lt;/span&gt; &lt;span class=&quot;token hvariable&quot;&gt;Decode.string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token hvariable&quot;&gt;Decode.field&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;age&quot;&lt;/span&gt; &lt;span class=&quot;token hvariable&quot;&gt;Decode.int&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;One way to test the decoder is to give it some example input and assert that we get correctly decoded data back.&lt;/p&gt;
&lt;pre class=&quot;language-elm&quot;&gt;&lt;code class=&quot;language-elm&quot;&gt;&lt;span class=&quot;token hvariable&quot;&gt;decoderTest&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;
    &lt;span class=&quot;token hvariable&quot;&gt;test&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;profile decoder&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;|&lt;/span&gt;
        \_ &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;
            &lt;span class=&quot;token hvariable&quot;&gt;Json.Decode.decodeString&lt;/span&gt; &lt;span class=&quot;token hvariable&quot;&gt;Profile.decoder&lt;/span&gt;
            &lt;span class=&quot;token string&quot;&gt;&quot;&quot;&quot;
            { &quot;name&quot;: &quot;Jane Doe&quot;, &quot;age&quot;: 42 }
            &quot;&quot;&quot;&lt;/span&gt;
                &lt;span class=&quot;token operator&quot;&gt;|&gt;&lt;/span&gt; &lt;span class=&quot;token hvariable&quot;&gt;Expect.equal&lt;/span&gt;
                    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token hvariable&quot;&gt;name&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Jane Doe&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token hvariable&quot;&gt;age&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;42&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Instead of writing examples by hand, we can generate a large number of random JSON documents that conform to the schema we wrote before and make sure that we can decode all of them. The &lt;a href=&quot;http://package.elm-lang.org/packages/NoRedInk/json-elm-schema/2.2.0/JsonSchema-Fuzz&quot;&gt;JsonSchema.Fuzz module&lt;/a&gt; provides just what we need.&lt;/p&gt;
&lt;pre class=&quot;language-elm&quot;&gt;&lt;code class=&quot;language-elm&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;module&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;ProfileTest&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;exposing&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token hvariable&quot;&gt;suite&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token import-statement&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; Test &lt;span class=&quot;token keyword&quot;&gt;exposing&lt;/span&gt; &lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token import-statement&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; JsonSchema.Fuzz &lt;span class=&quot;token keyword&quot;&gt;exposing&lt;/span&gt; &lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token hvariable&quot;&gt;schemaValue&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token import-statement&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; Schema.Profile&lt;/span&gt;
&lt;span class=&quot;token import-statement&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; Profile&lt;/span&gt;
&lt;span class=&quot;token import-statement&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; Json.Decode &lt;span class=&quot;token keyword&quot;&gt;exposing&lt;/span&gt; &lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token hvariable&quot;&gt;decodeValue&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token import-statement&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; Helpers &lt;span class=&quot;token keyword&quot;&gt;exposing&lt;/span&gt; &lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token hvariable&quot;&gt;expectOk&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;


&lt;span class=&quot;token hvariable&quot;&gt;suite&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;Test&lt;/span&gt;
&lt;span class=&quot;token hvariable&quot;&gt;suite&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;
    &lt;span class=&quot;token hvariable&quot;&gt;describe&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Profile&quot;&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;token hvariable&quot;&gt;fuzz&lt;/span&gt;
            &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token hvariable&quot;&gt;schemaValue&lt;/span&gt; &lt;span class=&quot;token hvariable&quot;&gt;Schema.Profile.schema&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
            &lt;span class=&quot;token string&quot;&gt;&quot;decoder complies with the profile JSON schema&quot;&lt;/span&gt;
          &lt;span class=&quot;token operator&quot;&gt;&amp;lt;|&lt;/span&gt;
            \&lt;span class=&quot;token hvariable&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;
                &lt;span class=&quot;token hvariable&quot;&gt;decodeValue&lt;/span&gt; &lt;span class=&quot;token hvariable&quot;&gt;Profile.decoder&lt;/span&gt; &lt;span class=&quot;token hvariable&quot;&gt;value&lt;/span&gt;
                    &lt;span class=&quot;token operator&quot;&gt;|&gt;&lt;/span&gt; &lt;span class=&quot;token hvariable&quot;&gt;expectOk&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;(Read more about &lt;a href=&quot;http://package.elm-lang.org/packages/elm-community/elm-test/4.2.0/Fuzz&quot;&gt;fuzzers&lt;/a&gt; and &lt;a href=&quot;http://package.elm-lang.org/packages/elm-community/elm-test/4.2.0/Test#fuzz&quot;&gt;fuzz tests&lt;/a&gt; in the elm-test documentation.)&lt;/p&gt;
&lt;p&gt;&lt;code&gt;expectOk&lt;/code&gt; is a custom expectation which takes a &lt;code&gt;Result&lt;/code&gt; (which &lt;a href=&quot;http://package.elm-lang.org/packages/elm-lang/core/5.1.1/Json-Decode#decodeValue&quot;&gt;&lt;code&gt;decodeValue&lt;/code&gt;&lt;/a&gt; returns) and only succeeds if that result is not an error.&lt;/p&gt;
&lt;pre class=&quot;language-elm&quot;&gt;&lt;code class=&quot;language-elm&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;module&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;Helpers&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;exposing&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token import-statement&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; Expect &lt;span class=&quot;token keyword&quot;&gt;exposing&lt;/span&gt; &lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;Expectation&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;


&lt;span class=&quot;token hvariable&quot;&gt;expectOk&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;Result&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;token hvariable&quot;&gt;value&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;Expect.Expectation&lt;/span&gt;
&lt;span class=&quot;token hvariable&quot;&gt;expectOk&lt;/span&gt; &lt;span class=&quot;token hvariable&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;case&lt;/span&gt; &lt;span class=&quot;token hvariable&quot;&gt;val&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;of&lt;/span&gt;
        &lt;span class=&quot;token constant&quot;&gt;Ok&lt;/span&gt; _ &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;
            &lt;span class=&quot;token hvariable&quot;&gt;Expect.pass&lt;/span&gt;

        &lt;span class=&quot;token constant&quot;&gt;Err&lt;/span&gt; &lt;span class=&quot;token hvariable&quot;&gt;err&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt;
            &lt;span class=&quot;token hvariable&quot;&gt;Expect.fail&lt;/span&gt; &lt;span class=&quot;token hvariable&quot;&gt;err&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I use this helper because JSON values are randomly generated and we do not know anything about their contents except whether they are valid according to the schema. Some decoders may still benefit from example-based tests where we do make assertions about the concrete output values.&lt;/p&gt;
&lt;p&gt;I have not yet needed JSON encoders on the Elm side, but &lt;code&gt;json-elm-schema&lt;/code&gt; also has a &lt;a href=&quot;http://package.elm-lang.org/packages/NoRedInk/json-elm-schema/2.2.0/JsonSchema-Validator&quot;&gt;validator module&lt;/a&gt; which can help test them.&lt;/p&gt;
&lt;h2&gt;Sharing the JSON schemas with Ruby&lt;/h2&gt;
&lt;p&gt;Next, we want to make the schemas available to the Ruby code. Elm cannot do file system operations, but the &lt;a href=&quot;https://www.npmjs.com/package/elm-json-schema-compiler&quot;&gt;elm-json-schema-compiler npm module&lt;/a&gt; allows you to compile the schemas defined in Elm code to regular JSON schema files. Unfortunately, I could not use it directly since I wanted some extra flexibility. Namely, I want to generate all the schemas in one go and keep the Elm &lt;code&gt;json-elm-schema&lt;/code&gt; package as a test dependency only.&lt;/p&gt;
&lt;p&gt;I modified the command line tool as shown below. There are two parts: an Elm program that encodes the schemas and sends them through a &lt;a href=&quot;https://guide.elm-lang.org/interop/javascript.html&quot;&gt;port&lt;/a&gt; and a Node.js program that compiles and runs the Elm program, saving the JSON strings to files as they come in through the port.&lt;/p&gt;
&lt;pre class=&quot;language-elm&quot;&gt;&lt;code class=&quot;language-elm&quot;&gt;&lt;span class=&quot;token hvariable&quot;&gt;port&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;module&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;Main&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;exposing&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token import-statement&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; JsonSchema &lt;span class=&quot;token keyword&quot;&gt;exposing&lt;/span&gt; &lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;Schema&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token import-statement&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; JsonSchema.Encoder &lt;span class=&quot;token keyword&quot;&gt;exposing&lt;/span&gt; &lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token hvariable&quot;&gt;encode&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token import-statement&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; Schema.Profile&lt;/span&gt;


&lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;alias&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;NamedSchema&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;Schema&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;


&lt;span class=&quot;token hvariable&quot;&gt;schemas&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;List&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;NamedSchema&lt;/span&gt;
&lt;span class=&quot;token hvariable&quot;&gt;schemas&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;profile&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token hvariable&quot;&gt;Schema.Profile.schema&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;


&lt;span class=&quot;token hvariable&quot;&gt;emitSchemas&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;List&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;NamedSchema&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;Cmd&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token hvariable&quot;&gt;emitSchemas&lt;/span&gt; &lt;span class=&quot;token hvariable&quot;&gt;namedSchemas&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;
    &lt;span class=&quot;token hvariable&quot;&gt;namedSchemas&lt;/span&gt;
        &lt;span class=&quot;token operator&quot;&gt;|&gt;&lt;/span&gt; &lt;span class=&quot;token hvariable&quot;&gt;List.map&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token hvariable&quot;&gt;encodeNamedSchema&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token hvariable&quot;&gt;emit&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token operator&quot;&gt;|&gt;&lt;/span&gt; &lt;span class=&quot;token hvariable&quot;&gt;Cmd.batch&lt;/span&gt;


&lt;span class=&quot;token hvariable&quot;&gt;encodeNamedSchema&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;NamedSchema&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token hvariable&quot;&gt;encodeNamedSchema&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;token hvariable&quot;&gt;title&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token hvariable&quot;&gt;schema&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;token hvariable&quot;&gt;title&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token hvariable&quot;&gt;encode&lt;/span&gt; &lt;span class=&quot;token hvariable&quot;&gt;schema&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;


&lt;span class=&quot;token hvariable&quot;&gt;main&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;Platform.Program&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;Never&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token hvariable&quot;&gt;main&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;
    &lt;span class=&quot;token hvariable&quot;&gt;Platform.program&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token hvariable&quot;&gt;init&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token hvariable&quot;&gt;emitSchemas&lt;/span&gt; &lt;span class=&quot;token hvariable&quot;&gt;schemas&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token hvariable&quot;&gt;update&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; \_ _ &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token hvariable&quot;&gt;Cmd.none&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token hvariable&quot;&gt;subscriptions&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; \_ &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt; &lt;span class=&quot;token hvariable&quot;&gt;Sub.none&lt;/span&gt;
        &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;



&lt;span class=&quot;token comment&quot;&gt;{-| Emits pairs of schema name and schema JSON string -}&lt;/span&gt;
&lt;span class=&quot;token hvariable&quot;&gt;port&lt;/span&gt; &lt;span class=&quot;token hvariable&quot;&gt;emit&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;-&gt;&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;Cmd&lt;/span&gt; &lt;span class=&quot;token hvariable&quot;&gt;a&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The important part for everyday use is the &lt;code&gt;schemas&lt;/code&gt; function where we declare the file name for each schema (the node script takes care of adding the &lt;code&gt;.json&lt;/code&gt; suffixes). In this example we only have one schema:&lt;/p&gt;
&lt;pre class=&quot;language-elm&quot;&gt;&lt;code class=&quot;language-elm&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;profile&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token hvariable&quot;&gt;Schema.Profile.schema&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The program which does all the input/output work takes two arguments: the path to the Elm file presented above and an output directory for the generated schemas.&lt;/p&gt;
&lt;pre class=&quot;language-javascript&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token hashbang comment&quot;&gt;#!/usr/bin/env node&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;process&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;argv&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;length &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token function&quot;&gt;fail&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token template-string&quot;&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;Generate JSON schema files using json-elm-schema
Usage:
  bin/generate-schemas &amp;lt;elm-schema-file&gt; &amp;lt;output-directory&gt;
&lt;/span&gt;&lt;span class=&quot;token template-punctuation string&quot;&gt;`&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; fs &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;fs&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; path &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;path&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; temp &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;temp&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;track&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; compiler &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;node-elm-compiler&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; sourcePath &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; path&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;resolve&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;process&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;argv&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; prefixPath &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; path&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;resolve&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;process&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;argv&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; targetPath &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; temp&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;path&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token literal-property property&quot;&gt;suffix&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;.js&#39;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

compiler&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;compileSync&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;sourcePath&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;yes&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;output&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; targetPath&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token literal-property property&quot;&gt;cwd&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;tests&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; Elm &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;targetPath&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; app &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Elm&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Main&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;worker&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

app&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;ports&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;emit&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;subscribe&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;payload&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; title &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; payload&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; json &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; payload&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; fileName &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; title &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;.json&#39;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; schemaPath &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; path&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;join&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;prefixPath&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; fileName&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

  fs&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;writeFile&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;schemaPath&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; json&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;err&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;err&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;throw&lt;/span&gt; err

    console&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;log&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;Wrote schema for &quot;&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt;
      title &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;&quot; to &#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; schemaPath&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;fail&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;msg&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  process&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;stderr&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;write&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;msg&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  process&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;exit&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In hindsight, there is probably a way to solve using the original npm package and a bash script, but on the upside I learned how to run &amp;quot;headless&amp;quot; Elm programs and carry out side-effects for them.&lt;/p&gt;
&lt;h2&gt;Schema validation in Ruby tests&lt;/h2&gt;
&lt;p&gt;Thanks to the &lt;a href=&quot;https://github.com/brandur/json_schema&quot;&gt;json_schema gem&lt;/a&gt; we can verify that the API server complies with the schema. Generate the schemas into a pre-defined directory such as &lt;code&gt;test/schemas&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ bin/generate-schemas tests/Schema/Main.elm test/schemas
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Don&#39;t forget to include this as a build step in your continuous integration pipeline to ensure you are always using the latest schemas.&lt;/p&gt;
&lt;p&gt;Define a test helper like so:&lt;/p&gt;
&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&#39;json_schema&#39;&lt;/span&gt;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;module&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;SchemaHelper&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token function&quot;&gt;assert_response_schema&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;schema_name&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    full_path &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;
      Rails&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;root&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;join&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&#39;test&#39;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&#39;schemas&#39;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; schema_name&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

    schema_data &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;JSON&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;parse&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;File&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;read&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;full_path&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    schema &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; JsonSchema&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;parse&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;schema_data&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    schema&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;expand_references&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;

    payload &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;JSON&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;parse&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;response&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;body&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

    schema&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;validate&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;payload&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This loads the specified schema and validates the server response against it. It is meant for use in a controller test.&lt;/p&gt;
&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&#39;test_helper&#39;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&#39;schema_helper&#39;&lt;/span&gt;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ProfilesControllerTest&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; ActionDispatch&lt;span class=&quot;token double-colon punctuation&quot;&gt;::&lt;/span&gt;IntegrationTest
  &lt;span class=&quot;token keyword&quot;&gt;include&lt;/span&gt; SchemaHelper

  test &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&#39;the output corresponds to the schema&#39;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;do&lt;/span&gt;
    get &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&#39;/profile&#39;&lt;/span&gt;&lt;/span&gt;

    assert_response &lt;span class=&quot;token symbol&quot;&gt;:success&lt;/span&gt;
    assert_response_schema &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&#39;profile.json&#39;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You could also modify the helper assertion to explicitly take the JSON structure as an argument, allowing you to unit test your JSON serializers.&lt;/p&gt;
</content>
    
  </entry>
  
    
  
  <entry>
    <title>A Self-Testing Test Library</title>
    <link href="https://dimiterpetrov.com/blog/self-testing-test-library/"/>
    <updated>2016-08-18T00:00:00Z</updated>
    <id>https://dimiterpetrov.com/blog/self-testing-test-library/</id>
    
    <content type="html">&lt;p&gt;After watching Ryan Davis present a way to &lt;a href=&quot;https://www.youtube.com/watch?v=VPr5pmlAq20&quot;&gt;write a test framework from scratch&lt;/a&gt;, I knew I wanted to try that on my own. Before I could get to it, I stumbled upon a video of Kent Beck writing a &lt;a href=&quot;https://www.youtube.com/watch?v=nIonZ6-4nuU&quot;&gt;small self-tested test library in CoffeeScript&lt;/a&gt;. In it, Kent Beck demonstrates the effect immediate feedback has on a test-driven workflow, but it was the self-testing aspect that fascinated me.&lt;/p&gt;
&lt;p&gt;I set out to write a Ruby self-tested library from scratch, describing the process as I go. You can follow along with the project&#39;s git &lt;a href=&quot;https://github.com/crackofdusk/raisin&quot;&gt;repository&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Assertions&lt;/h2&gt;
&lt;p&gt;Assertions are the building blocks of this test library. We start with the &lt;code&gt;assert&lt;/code&gt; function, which takes a boolean parameter. It does nothing if the parameter evaluates to &lt;code&gt;true&lt;/code&gt; and complains if it evaluates to &lt;code&gt;false&lt;/code&gt;. In Ruby, raising an exception is one way to signal failure. It is convenient for &lt;code&gt;assert&lt;/code&gt; since uncaught exceptions interrupt the program. Incidentally, I named the library Raisin since we will be raisin&#39; exceptions.&lt;/p&gt;
&lt;p&gt;As we said, &lt;code&gt;assert&lt;/code&gt; does nothing if the provided value is &lt;code&gt;true&lt;/code&gt;. Let&#39;s write our very first failing test:&lt;/p&gt;
&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;assert&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

puts &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Success!&quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;puts &amp;quot;Success!&amp;quot;&lt;/code&gt; should stay at the very bottom of the file as we add new code. Run the script:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;ruby raisin.rb&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It does not print &amp;quot;Success!&amp;quot; yet, because &lt;code&gt;assert&lt;/code&gt; is undefined. As an aside, I defined &lt;code&gt;,t&lt;/code&gt; as a shortcut in vim (in normal mode press comma, then press &amp;quot;t&amp;quot;) to run the tests, i.e. the current file as the code is self-tested.&lt;/p&gt;
&lt;pre class=&quot;language-vim&quot;&gt;&lt;code class=&quot;language-vim&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token builtin&quot;&gt;noremap&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;leader&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;t&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;ruby&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;%&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt;CR&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Defining the function with an empty implementation makes the test pass:&lt;/p&gt;
&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token function&quot;&gt;assert&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;condition&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now let&#39;s cover the other intended behaviour: parameters that do not evaluate to &lt;code&gt;true&lt;/code&gt; cause an exception.&lt;/p&gt;
&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;AssertionError&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; StandardError
&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;NothingRaised&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; StandardError
&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;

raised &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;begin&lt;/span&gt;
  assert&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;rescue&lt;/span&gt; AssertionError
  raised &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;raise&lt;/span&gt; NothingRaised &lt;span class=&quot;token keyword&quot;&gt;unless&lt;/span&gt; raised&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We define &lt;code&gt;AssertionError&lt;/code&gt; for use within our assertions. That way we have meaningful error messages: we know specifically that an assertion failed and not something else in the code. In a similar manner, we define &lt;code&gt;NothingRaised&lt;/code&gt;, but just for the purposes of this test. If the assertion fails, we print &amp;quot;Success!&amp;quot;, otherwise we raise &lt;code&gt;NothingRaised&lt;/code&gt; so we know something is wrong.&lt;/p&gt;
&lt;p&gt;We run the script to confirm the test failure:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;raisin.rb:18:in `&amp;lt;main&amp;gt;&#39;: NothingRaised (NothingRaised)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then we implement &lt;code&gt;assert&lt;/code&gt; to make the test pass.&lt;/p&gt;
&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token function&quot;&gt;assert&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;condition&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;raise&lt;/span&gt; AssertionError &lt;span class=&quot;token keyword&quot;&gt;unless&lt;/span&gt; raised
&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;(Commit &lt;a href=&quot;https://github.com/crackofdusk/raisin/commit/eb31d6e93c33e812e276899f980995e987d4fbb2&quot;&gt;eb31d6e&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;&lt;code&gt;assert_equal&lt;/code&gt; is an assertion that I use most of the time. We can implement this on top of &lt;code&gt;assert&lt;/code&gt;. Add an empty implementation and a failing test:&lt;/p&gt;
&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token function&quot;&gt;assert_equal&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;expected&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; actual&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;

assert_equal&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

raised &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;begin&lt;/span&gt;
  assert_equal&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;foo&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;bar&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;rescue&lt;/span&gt; AssertionError
  raised &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;raise&lt;/span&gt; NothingRaised &lt;span class=&quot;token keyword&quot;&gt;unless&lt;/span&gt; raised&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We are repeating the structure of that second test, but we can refactor that after we make it pass.&lt;/p&gt;
&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token function&quot;&gt;assert_equal&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;expected&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; actual&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  assert&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;expected &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; actual&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Let&#39;s now refactor the verification that &lt;code&gt;AssertionError&lt;/code&gt; was raised.&lt;/p&gt;
&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token function&quot;&gt;test_failure&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;block&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  raised &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;begin&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;yield&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;rescue&lt;/span&gt; AssertionError
    raised &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;raise&lt;/span&gt; NothingRaised &lt;span class=&quot;token keyword&quot;&gt;unless&lt;/span&gt; raised
&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;

test_failure &lt;span class=&quot;token keyword&quot;&gt;do&lt;/span&gt;
  assert&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;

test_failure &lt;span class=&quot;token keyword&quot;&gt;do&lt;/span&gt;
  assert_equal&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;foo&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;bar&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;test_failure&lt;/code&gt; takes a block so that we can execute it at the right spot in the test. If we took the assertion as an argument (&lt;code&gt;test_failure(assert(false))&lt;/code&gt;), then the assertion would be evaluated before the code in &lt;code&gt;test_failure&lt;/code&gt; and we wouldn&#39;t be able to catch the &lt;code&gt;AssertionError&lt;/code&gt;. Since &lt;code&gt;test_failure&lt;/code&gt; is a helper function in our own tests, we are not going to write a test for it, but still want to test it manually. Comment out the implementation of &lt;code&gt;assert&lt;/code&gt; and confirm that the tests fail:&lt;/p&gt;
&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token function&quot;&gt;assert&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;condition&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token comment&quot;&gt;#raise AssertionError unless condition&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;

test_failure &lt;span class=&quot;token keyword&quot;&gt;do&lt;/span&gt;
  assert&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;raisin.rb:14:in `test_failure&#39;: NothingRaised (NothingRaised)
        from raisin.rb:23:in `&amp;lt;main&amp;gt;&#39;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;All good.&lt;/p&gt;
&lt;p&gt;(Commit &lt;a href=&quot;https://github.com/crackofdusk/raisin/commit/d6b9a3eaabb04a41ab9686208d8ac9117c064345&quot;&gt;d6b9a3e&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;In the case of a failing &lt;code&gt;assert_equal&lt;/code&gt; we should get more information than just &lt;code&gt;AssertionError&lt;/code&gt;. Exceptions optionally carry messages. We can use those to convey details about assertion failures.&lt;/p&gt;
&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token function&quot;&gt;assert&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;condition&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; failure_reason &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;nil&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;unless&lt;/span&gt; condition
    &lt;span class=&quot;token keyword&quot;&gt;raise&lt;/span&gt; AssertionError&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; failure_reason
  &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now we can update &lt;code&gt;assert_equal&lt;/code&gt; to report what went wrong:&lt;/p&gt;
&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token function&quot;&gt;assert_equal&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;expected&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; actual&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  message &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Expected &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;token content&quot;&gt;expected&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;inspect&lt;/span&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;, got &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;token content&quot;&gt;actual&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;inspect&lt;/span&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;
  assert&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;expected &lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt; actual&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; message&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Let&#39;s try it:&lt;/p&gt;
&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;assert_equal&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;foo&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;bar&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;raisin.rb:19:in `assert&#39;: Expected &amp;quot;foo&amp;quot;, got &amp;quot;bar&amp;quot; (AssertionError)
        from raisin.rb:32:in `assert_equal&#39;
        from raisin.rb:41:in `&amp;lt;main&amp;gt;&#39;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Without the calls to &lt;a href=&quot;http://ruby-doc.org/core-2.3.1/Object.html#method-i-inspect&quot;&gt;&lt;code&gt;inspect&lt;/code&gt;&lt;/a&gt; the printed message would be &lt;code&gt;Expected foo, got bar&lt;/code&gt; which is less readable than when the strings are quoted.&lt;/p&gt;
&lt;p&gt;Do we test the message? I feel like that would be testing Ruby itself, which is not very useful. What we can do is test the message format later if we decide to add pretty printing.&lt;/p&gt;
&lt;p&gt;(Commit &lt;a href=&quot;https://github.com/crackofdusk/raisin/commit/0b9fa6b356927527f582a0d0be5beabb293a3218&quot;&gt;0b9fa6b&lt;/a&gt;)&lt;/p&gt;
&lt;h2&gt;Test suites&lt;/h2&gt;
&lt;p&gt;Let&#39;s declare a test suite that uses our library and run it.&lt;/p&gt;
&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token function&quot;&gt;greet&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;name &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;nil&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&#39;Hello&#39;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; name&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;compact&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;join&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;, &quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;!&quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
  &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; assert_equal&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Hello, Bob!&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; greet&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Bob&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; assert_equal&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Hello!&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; greet&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;map&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;token symbol&quot;&gt;:call&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here, each lambda is a test case. The problem with lambdas is that they leak state between tests. Consider these tests:&lt;/p&gt;
&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;x &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
  &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; assert_equal &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; x &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; x &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; x &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; assert_equal &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; x &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token operator&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; assert_equal &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; x &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;map&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;token symbol&quot;&gt;:call&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The third test fails:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;raisin.rb:19:in `assert&#39;: Expected 1, got 2 (AssertionError)
        from raisin.rb:32:in `assert_equal&#39;
        from raisin.rb:63:in `block in &amp;lt;main&amp;gt;&#39;
        from raisin.rb:64:in `map&#39;
        from raisin.rb:64:in `&amp;lt;main&amp;gt;&#39;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Methods keep state isolated. We will represent test cases as methods.&lt;/p&gt;
&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token function&quot;&gt;test_with_name&lt;/span&gt;&lt;/span&gt;
  assert_equal&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Hello, Bob!&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; greet&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Bob&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token function&quot;&gt;test_without_name&lt;/span&gt;&lt;/span&gt;
  assert_equal&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Hello!&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; greet&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;

test_with_name
test_without_name&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now our tests also have names to convey their purpose. The downside is that we need to call each function. We can overcome that with a little meta-programming.&lt;/p&gt;
&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;GreetingTestSuite&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token function&quot;&gt;test_with_name&lt;/span&gt;&lt;/span&gt;
    assert_equal&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Hello, Bob!&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; greet&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Bob&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token function&quot;&gt;test_without_name&lt;/span&gt;&lt;/span&gt;
    assert_equal&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Hello!&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; greet&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;

suite &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;GreetingTestSuite&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt;
suite&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;public_methods&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;each&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;test&lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;
  suite&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;send&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;test&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We take advantage of the &lt;a href=&quot;http://ruby-doc.org/core-2.3.1/Object.html#method-i-public_methods&quot;&gt;&lt;code&gt;public_methods&lt;/code&gt;&lt;/a&gt; method. We pass it &lt;code&gt;false&lt;/code&gt; to only get methods that we have defined and not superclass methods. Compare:&lt;/p&gt;
&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;suite&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;public_methods&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token symbol&quot;&gt;:test_with_name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;:test_without_name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;:instance_of?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;:public_send&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;:instance_variable_get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;:instance_variable_set&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;:instance_variable_defined?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;:remove_instance_variable&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;:private_methods&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;:kind_of?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;:instance_variables&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;:tap&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;:method&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;:public_method&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;:singleton_method&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;:is_a?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;:extend&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;:define_singleton_method&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;:to_enum&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;:enum_for&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;lt;=&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=~&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!~&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;:eql?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;:respond_to?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;:freeze&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;:inspect&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;:display&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;:object_id&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;:send&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;:to_s&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;:nil?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;:hash&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;:class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;:singleton_class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;:clone&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;:dup&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;:itself&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;:taint&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;:tainted?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;:untaint&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;:untrust&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;:trust&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;:untrusted?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;:methods&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;:protected_methods&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;:frozen?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;:public_methods&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;:singleton_methods&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;==&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;:__send__&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;:equal?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;:instance_eval&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;:instance_exec&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;:__id__&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and&lt;/p&gt;
&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;suite&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;public_methods&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token symbol&quot;&gt;:test_with_name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;:test_without_name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Once we have the correct method names we send those messages to the suite object. That code is reusable as is so we can put it in a method that takes a class instance as parameter.&lt;/p&gt;
&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token function&quot;&gt;run_suite&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;suite&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  suite&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;public_methods&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;each&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;test&lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;
    suite&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;send&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;test&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;(Commit &lt;a href=&quot;https://github.com/crackofdusk/raisin/commit/edf2e31d726549fd6078d18163d84ac82ab51a3c&quot;&gt;edf2e31&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;Grouping the tests in a class also allows us to perform setup and tear-down if the test suite has any.&lt;/p&gt;
&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token function&quot;&gt;run_suite&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;suite&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  suite&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;public_methods&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;each&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;test&lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;
    suite&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;send&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token symbol&quot;&gt;:setup&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; suite&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;respond_to&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token symbol&quot;&gt;:setup&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    suite&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;send&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;test&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    suite&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;send&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token symbol&quot;&gt;:teardown&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; suite&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;respond_to&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token symbol&quot;&gt;:teardowon&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;While I prefer a functional approach in general, I identify two code smells in the above code. First, the method is called &lt;code&gt;run_suite&lt;/code&gt; and takes a &lt;code&gt;suite&lt;/code&gt; parameter. It would make more sense to call &lt;code&gt;suite.run&lt;/code&gt;. Second, we are checking if an object responds to a message before sending it that message. A more confident approach is to always run &lt;code&gt;setup&lt;/code&gt; and &lt;code&gt;teardown&lt;/code&gt;, but provide default implementations. We already have a special case of a test suite in the form of the example &lt;code&gt;GreetingTestSuite&lt;/code&gt;. What we are missing is a generalized version:&lt;/p&gt;
&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;TestSuite&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token function&quot;&gt;run&lt;/span&gt;&lt;/span&gt;
    test_names &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; public_methods&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;grep&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token regex-literal&quot;&gt;&lt;span class=&quot;token regex&quot;&gt;/^test_/&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

    test_names&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;each&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;test&lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;
      setup
      send&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;test&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      teardown
    &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token function&quot;&gt;setup&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token function&quot;&gt;teardown&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The adopted convention is that the names of the methods that are test cases begin with &lt;code&gt;test_&lt;/code&gt;. That way we make sure to not erroneously call the &lt;code&gt;setup&lt;/code&gt; and &lt;code&gt;teardown&lt;/code&gt; methods. It also allows test suites to contain helper methods.&lt;/p&gt;
&lt;p&gt;We need to to update the test code to use our new class:&lt;/p&gt;
&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;GreetingTestSuite&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; TestSuite
  &lt;span class=&quot;token comment&quot;&gt;# ...&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;token class-name&quot;&gt;GreetingTestSuite&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;run&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;(Commit &lt;a href=&quot;https://github.com/crackofdusk/raisin/commit/67b9a5d0f29a718eabf8156ed7f6b74aea067df0&quot;&gt;67b9a5d&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;With multiple test suites we have to run each one manually, just like we did with test cases before that. We can automate that by keeping track of classes that inherit from &lt;code&gt;TestSuite&lt;/code&gt; and running all of those test suites.&lt;/p&gt;
&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;TestSuite&lt;/span&gt;
  &lt;span class=&quot;token variable&quot;&gt;@suites&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;inherited&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;suite&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token variable&quot;&gt;@suites&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; suite
  &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;run&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token variable&quot;&gt;@suites&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;each&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;suite&lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;
      suite&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;run
    &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;token comment&quot;&gt;#...&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We cannot yet get rid of &lt;code&gt;GreetingTestSuite.new.run&lt;/code&gt; in favour of calling &lt;code&gt;TestSuite.run&lt;/code&gt; at the very bottom of our file because of a subtlety with nested test suites that we will address later.&lt;/p&gt;
&lt;p&gt;(Commit &lt;a href=&quot;https://github.com/crackofdusk/raisin/commit/d6f15e6b678221bcd8a5bd7c5e18cc307ce96592&quot;&gt;d6f15e6&lt;/a&gt;)&lt;/p&gt;
&lt;h2&gt;Reporting&lt;/h2&gt;
&lt;p&gt;In the current implementation failing tests manifest themselves by raising an error, but there is no way to distinguish between a test that has not been run and a test that passes. After each step in the previous section, I&#39;ve been manually verifying that the tests are still running by making assertions fail. That reveals two things:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;the test suite functionality is not tested rigorously;&lt;/li&gt;
&lt;li&gt;reporting success is as important as reporting failure.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Let&#39;s address the first one by counting how many tests have been run. This will allow us to assert that all tests in a suite are being run.&lt;/p&gt;
&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;DummySuite&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; TestSuite
  &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token function&quot;&gt;test_equality&lt;/span&gt;&lt;/span&gt;
    assert_equal&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;

assert_equal &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;DummySuite&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;run&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;runs&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We got rid of the greetings code and associated tests in favor of a simpler example. The assertion fails with the following error:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;raisin.rb:77:in `&amp;lt;main&amp;gt;&#39;: undefined method `runs&#39; for [:test_equality]:Array (NoMethodError)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is because &lt;code&gt;TestSuite#run&lt;/code&gt; implicitly returns the array of test names. We are going to return a report instead.&lt;/p&gt;
&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Report&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;TestSuite&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token function&quot;&gt;run&lt;/span&gt;&lt;/span&gt;
    test_names &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; public_methods&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;grep&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token regex-literal&quot;&gt;&lt;span class=&quot;token regex&quot;&gt;/^test_/&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

    test_names&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;each&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;test&lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;
      setup
      send&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;test&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      teardown
    &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;

    &lt;span class=&quot;token class-name&quot;&gt;Report&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Running the tests again gives us:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;raisin.rb:82:in `&amp;lt;main&amp;gt;&#39;: undefined method `runs&#39; for #&amp;lt;Report:0x00555e6f553548&amp;gt; (NoMethodError)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With an empty implementation of &lt;code&gt;runs&lt;/code&gt; in &lt;code&gt;Report&lt;/code&gt;, we finally get an assertion error (&lt;code&gt;Expected 1, got nil&lt;/code&gt;). We can make the test pass with the following:&lt;/p&gt;
&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;TestSuite&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token function&quot;&gt;run&lt;/span&gt;&lt;/span&gt;
    runs &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;
    test_names &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; public_methods&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;grep&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token regex-literal&quot;&gt;&lt;span class=&quot;token regex&quot;&gt;/^test_/&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

    test_names&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;each&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;test&lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;
      setup
      send&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;test&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      runs &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; runs &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;
      teardown
    &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;

    &lt;span class=&quot;token class-name&quot;&gt;Report&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;runs&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Report&lt;/span&gt;
  attr_reader &lt;span class=&quot;token symbol&quot;&gt;:runs&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token function&quot;&gt;initialize&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;runs&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token variable&quot;&gt;@runs&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; runs
  &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We can test that only methods starting with &lt;code&gt;test_&lt;/code&gt; are called by adding an extra method to &lt;code&gt;DummySuite&lt;/code&gt; called &lt;code&gt;foo&lt;/code&gt; for example. This does not fail the assertion, as expected.&lt;/p&gt;
&lt;p&gt;The next step is to count the failed tests. I will spare you the TDD process. Here is one possible implementation:&lt;/p&gt;
&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;TestSuite&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token function&quot;&gt;run&lt;/span&gt;&lt;/span&gt;
    runs &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;
    failures &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;
    test_names &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; public_methods&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;grep&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token regex-literal&quot;&gt;&lt;span class=&quot;token regex&quot;&gt;/^test_/&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

    test_names&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;each&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;test&lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;
      setup
      &lt;span class=&quot;token keyword&quot;&gt;begin&lt;/span&gt;
        send&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;test&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;rescue&lt;/span&gt; AssertionError
        failures &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; failures &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;
      runs &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; runs &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;
      teardown
    &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;

    &lt;span class=&quot;token class-name&quot;&gt;Report&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;runs&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; failures&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Report&lt;/span&gt;
  attr_reader &lt;span class=&quot;token symbol&quot;&gt;:runs&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;:failures&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token function&quot;&gt;initialize&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;runs&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; failures&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token variable&quot;&gt;@runs&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; runs
    &lt;span class=&quot;token variable&quot;&gt;@failures&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; failures
  &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;DummySuite&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; TestSuite
  &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token function&quot;&gt;test_equality&lt;/span&gt;&lt;/span&gt;
    assert_equal&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;# should fail&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token function&quot;&gt;test_the_truth&lt;/span&gt;&lt;/span&gt;
    assert&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;

result &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;DummySuite&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;run
assert_equal &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; result&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;runs
assert_equal &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; result&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;failures&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;(Commit &lt;a href=&quot;https://github.com/crackofdusk/raisin/commit/72f91c36b1356a9a82f48bc6e0f4cacfa6c16146&quot;&gt;72f91c3&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;We should also show progress as tests run. I particularly like the convention of printing a dot when a test passes and the letter &amp;quot;F&amp;quot; when it fails. The easiest way for now is to add &lt;code&gt;print&lt;/code&gt; statements inside &lt;code&gt;TestSuite#run&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token function&quot;&gt;run&lt;/span&gt;&lt;/span&gt;
  runs &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;
  failures &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;
  test_names &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; public_methods&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;grep&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token regex-literal&quot;&gt;&lt;span class=&quot;token regex&quot;&gt;/^test_/&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

  test_names&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;each&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;test&lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;
    setup
    &lt;span class=&quot;token keyword&quot;&gt;begin&lt;/span&gt;
      send&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;test&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      print &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;.&quot;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;rescue&lt;/span&gt; AssertionError
      failures &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; failures &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;
      print &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;F&quot;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;
    runs &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; runs &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;
    teardown
  &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;

  puts

  &lt;span class=&quot;token class-name&quot;&gt;Report&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;runs&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; failures&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That works, but the method is getting a little unwieldy. It does too much: it selects which methods to run, it gathers statistics, it prints to the terminal. The test suite itself shouldn&#39;t need to know how to do all that. First, we&#39;ll introduce a collaborator that hides the exception handling:&lt;/p&gt;
&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;TestResult&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;from&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;block&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;begin&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;yield&lt;/span&gt;
      &lt;span class=&quot;token class-name&quot;&gt;TestSuccess&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;rescue&lt;/span&gt; AssertionError
      &lt;span class=&quot;token class-name&quot;&gt;TestFailure&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;TestSuccess&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token function&quot;&gt;success&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;
    &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;TestFailure&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token function&quot;&gt;success&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;
    &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We use it like this:&lt;/p&gt;
&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;TestSuite&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token function&quot;&gt;run&lt;/span&gt;&lt;/span&gt;
    runs &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;
    failures &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;
    test_names &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; public_methods&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;grep&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token regex-literal&quot;&gt;&lt;span class=&quot;token regex&quot;&gt;/^test_/&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

    test_names&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;each&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;test&lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;
      result &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; TestResult&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;from &lt;span class=&quot;token keyword&quot;&gt;do&lt;/span&gt;
        setup
        send&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;test&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        teardown
      &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;

      &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; result&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;success&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;
        print &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;.&quot;&lt;/span&gt;&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt;
        failures &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; failures &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;
        print &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;F&quot;&lt;/span&gt;&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;
      runs &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; runs &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;

    puts

    &lt;span class=&quot;token class-name&quot;&gt;Report&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;runs&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; failures&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Notice that I&#39;ve included the setup and tear-down in the block. It&#39;s a matter of precaution, in case they contain assertions as well. Also, running the test and displaying the result used to be interleaved and now they are done one after the other, allowing us to extract the reporting code. The report class becomes:&lt;/p&gt;
&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Report&lt;/span&gt;
  attr_reader &lt;span class=&quot;token symbol&quot;&gt;:runs&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;:failures&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token function&quot;&gt;initialize&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token variable&quot;&gt;@runs&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;
    &lt;span class=&quot;token variable&quot;&gt;@failures&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token function&quot;&gt;add_result&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;result&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; result&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;success&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;
      print &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;.&quot;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt;
      &lt;span class=&quot;token variable&quot;&gt;@failures&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;@failures&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;
      print &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;F&quot;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;
    &lt;span class=&quot;token variable&quot;&gt;@runs&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;@runs&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here is the improved test runner:&lt;/p&gt;
&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;TestSuite&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token function&quot;&gt;run&lt;/span&gt;&lt;/span&gt;
    report &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Report&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt;
    test_names &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; public_methods&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;grep&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token regex-literal&quot;&gt;&lt;span class=&quot;token regex&quot;&gt;/^test_/&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

    test_names&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;each&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;test&lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;
      result &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; TestResult&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;from &lt;span class=&quot;token keyword&quot;&gt;do&lt;/span&gt;
        setup
        send&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;test&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        teardown
      &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;
      report&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;add_result&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;result&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;
    report
  &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;(Commit &lt;a href=&quot;https://github.com/crackofdusk/raisin/commit/d70782568a06e43e62be1c231f7687bbe1d405fa&quot;&gt;d707825&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;One of the things we haven&#39;t tested is the report output. This requires a small modification of the report class. We will pass the input/output stream to the constructor. That way we can use &lt;code&gt;$stdout&lt;/code&gt; when we want to display something in the terminal and an instance of &lt;a href=&quot;http://ruby-doc.org/stdlib-2.3.1/libdoc/stringio/rdoc/StringIO.html&quot;&gt;&lt;code&gt;StringIO&lt;/code&gt;&lt;/a&gt; when we need to capture the output for tests. In our test case, we are going to simulate a suite that has one failing and one passing test. The resulting output should be an &amp;quot;F&amp;quot; followed by a dot. We reuse the &lt;code&gt;DummySuite&lt;/code&gt; from before, but we capture the output.&lt;/p&gt;
&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;output &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;StringIO&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt;
report &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;DummySuite&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;run&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;output&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
assert_equal&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; report&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;runs&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
assert_equal&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; report&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;failures&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
assert_equal&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;F.&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; output&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;string&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We modify &lt;code&gt;Report&lt;/code&gt; as described above:&lt;/p&gt;
&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Report&lt;/span&gt;
  attr_reader &lt;span class=&quot;token symbol&quot;&gt;:runs&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token symbol&quot;&gt;:failures&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token function&quot;&gt;initialize&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;io&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token variable&quot;&gt;@io&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; io
    &lt;span class=&quot;token variable&quot;&gt;@runs&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;
    &lt;span class=&quot;token variable&quot;&gt;@failures&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token function&quot;&gt;add_result&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;result&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; result&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;success&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;
      io&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;print &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;.&quot;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt;
      &lt;span class=&quot;token variable&quot;&gt;@failures&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;@failures&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;
      io&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;print &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;F&quot;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;
    &lt;span class=&quot;token variable&quot;&gt;@runs&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;@runs&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt;

  attr_accessor &lt;span class=&quot;token symbol&quot;&gt;:io&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Finally, we update &lt;code&gt;TestSuite#run&lt;/code&gt; to use the new API:&lt;/p&gt;
&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;TestSuite&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token function&quot;&gt;run&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;io &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$stdout&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    report &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Report&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;io&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;# ...&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;(Commit &lt;a href=&quot;https://github.com/crackofdusk/raisin/commit/3aff6fc6cac884ae7a7355c42c6e367d4522027e&quot;&gt;3aff6fc&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;By just writing &amp;quot;F&amp;quot; when a test fails we lose valuable information about the reason of the failure. We can store the error and print a nice summary of all errors when the test suite finishes running. Let&#39;s make a test test suite (you read that right) to test the summary.&lt;/p&gt;
&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;suite &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;TestSuite&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;do&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token function&quot;&gt;test_1&lt;/span&gt;&lt;/span&gt;
    assert&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;failure1&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token function&quot;&gt;test_2&lt;/span&gt;&lt;/span&gt;
    assert&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;failure2&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;
output &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;StringIO&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt;
report &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; suite&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;run&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;output&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
assert&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;output&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;string&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;include&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;failure1&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Report does not include error details&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
assert&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;output&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;string&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;include&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;failure2&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Report does not include error details&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
assert&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;output&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;string&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;include&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;2 runs, 2 failures&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Report does not include statistics&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I created an anonymous class, because we don&#39;t really care about reusing it. To implement this, we first need to store the error messages for failed tests:&lt;/p&gt;
&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;TestResult&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;from&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;block&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;begin&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;yield&lt;/span&gt;
      &lt;span class=&quot;token class-name&quot;&gt;TestSuccess&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;rescue&lt;/span&gt; AssertionError &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; error
      &lt;span class=&quot;token class-name&quot;&gt;TestFailure&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;error&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;TestFailure&lt;/span&gt;
  attr_reader &lt;span class=&quot;token symbol&quot;&gt;:error&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token function&quot;&gt;initialize&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;error&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token variable&quot;&gt;@error&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; error
  &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token function&quot;&gt;success&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;
    &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then we gather them in the report and print them at the end:&lt;/p&gt;
&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Report&lt;/span&gt;
  attr_reader &lt;span class=&quot;token symbol&quot;&gt;:runs&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token function&quot;&gt;initialize&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;io&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token variable&quot;&gt;@io&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; io
    &lt;span class=&quot;token variable&quot;&gt;@runs&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt;
    &lt;span class=&quot;token variable&quot;&gt;@errors&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token function&quot;&gt;add_result&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;result&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; result&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;success&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;
      io&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;print &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;.&quot;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt;
      &lt;span class=&quot;token variable&quot;&gt;@errors&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt; result&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;error
      io&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;print &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;F&quot;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;
    &lt;span class=&quot;token variable&quot;&gt;@runs&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;@runs&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token function&quot;&gt;failures&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token variable&quot;&gt;@errors&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;count
  &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token function&quot;&gt;summarize&lt;/span&gt;&lt;/span&gt;
    io&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;puts
    &lt;span class=&quot;token variable&quot;&gt;@failures&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;each&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;failure&lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;
      io&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;puts
      io&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;puts failure&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;message
      io&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;puts failure&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;backtrace
      io&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;puts
    &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;
    io&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;puts
    io&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;puts &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;token content&quot;&gt;runs&lt;/span&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt; runs, &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;token content&quot;&gt;failures&lt;/span&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt; failures&quot;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt;

  attr_accessor &lt;span class=&quot;token symbol&quot;&gt;:io&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Finally, we call summarize from &lt;code&gt;TestSuite#run&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;TestSuite&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token function&quot;&gt;run&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;io &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$stdout&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;# ...&lt;/span&gt;
    report&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;summarize
    report
  &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We also need to update an assertion in the previous test case from&lt;/p&gt;
&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;assert_equal&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;F.&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; output&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;string&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;to&lt;/p&gt;
&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;assert&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;output&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;string&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;include&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;F.&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;(Commit &lt;a href=&quot;https://github.com/crackofdusk/raisin/commit/175d0a464971abae7849ebc757cff9d1014a8e9b&quot;&gt;175d0a4&lt;/a&gt;)&lt;/p&gt;
&lt;h2&gt;Test suites testing test suites&lt;/h2&gt;
&lt;p&gt;Now that we can organize tests and see them fail, we should refactor.&lt;/p&gt;
&lt;p&gt;Include the library in a new file where the new test suites will reside:&lt;/p&gt;
&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;&lt;span class=&quot;token comment&quot;&gt;# test.rb&lt;/span&gt;
require_relative &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&#39;./raisin&#39;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We&#39;ll be porting our tests into that suite. What this gives is an opportunity to organize our existing tests, but also test the test suite functionality using itself. I added some tests, making sure to see each one fail before fixing it and getting to the next:&lt;/p&gt;
&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;NothingRaised&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; StandardError
&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;AssertionTests&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; TestSuite
  &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token function&quot;&gt;test_true&lt;/span&gt;&lt;/span&gt;
    assert&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token function&quot;&gt;test_truthy&lt;/span&gt;&lt;/span&gt;
    assert&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    assert&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    assert&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;foo&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    assert&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Object&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token function&quot;&gt;test_false&lt;/span&gt;&lt;/span&gt;
    assert_error &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; assert&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token function&quot;&gt;test_falsy&lt;/span&gt;&lt;/span&gt;
    assert_error &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; assert&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;nil&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token function&quot;&gt;test_equal&lt;/span&gt;&lt;/span&gt;
    assert_equal&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;foo&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&#39;foo&#39;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    assert_equal&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token function&quot;&gt;test_not_equal&lt;/span&gt;&lt;/span&gt;
    assert_error &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; assert_equal&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
    assert_error &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; assert_equal&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;foo&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;bar&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token function&quot;&gt;assert_error&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;block&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    raised &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;begin&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;yield&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;rescue&lt;/span&gt; AssertionError
      raised &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;raise&lt;/span&gt; NothingRaised &lt;span class=&quot;token keyword&quot;&gt;unless&lt;/span&gt; raised
  &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;token class-name&quot;&gt;AssertionTests&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;run&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We make another suite to test the output of running a test suite:&lt;/p&gt;
&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;OutputTests&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; TestSuite
  &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token function&quot;&gt;test_statistics&lt;/span&gt;&lt;/span&gt;
    suite &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;TestSuite&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;do&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token function&quot;&gt;test_equality&lt;/span&gt;&lt;/span&gt;
        assert_equal&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;

      &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token function&quot;&gt;test_the_truth&lt;/span&gt;&lt;/span&gt;
        assert&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;

    output &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;StringIO&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt;
    report &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; suite&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;run&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;output&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    assert_equal&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; report&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;runs&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    assert_equal&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; report&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;failures&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    assert&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;output&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;string&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;include&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;2 runs, 1 failures&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
           &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Report does not include statistics&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token function&quot;&gt;test_summary&lt;/span&gt;&lt;/span&gt;
    suite &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;TestSuite&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;do&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token function&quot;&gt;test_1&lt;/span&gt;&lt;/span&gt;
        assert&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;failure1&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;

      &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token function&quot;&gt;test_2&lt;/span&gt;&lt;/span&gt;
        assert&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;failure2&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;
    output &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;StringIO&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt;
    suite&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;run&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;output&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    assert&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;output&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;string&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;include&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;failure1&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
           &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Report does not include error details&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    assert&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;output&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;string&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;include&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;failure2&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
           &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Report does not include error details&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;token class-name&quot;&gt;OutputTests&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;run&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;(Commit &lt;a href=&quot;https://github.com/crackofdusk/raisin/commit/3d47bcffd0da6c95e5ee2c36a723617d23b960b7&quot;&gt;3d47bcf&lt;/a&gt;. I also grouped the assertions in a module in commit &lt;a href=&quot;https://github.com/crackofdusk/raisin/commit/cfcdd4138e3f31e84f664391c3c1da074f9a818c&quot;&gt;cfcdd41&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;Notice that we&#39;re instantiating and running the test suites individually. That&#39;s because executing &lt;code&gt;TestSuite.run&lt;/code&gt; will run all registered suites, including the tests suites that fail on purpose because they are part of a test case.&lt;/p&gt;
&lt;p&gt;Since we run the inner test suites directly, we can remove them from &lt;code&gt;TestSuite&lt;/code&gt;&#39;s suites. That way they won&#39;t be run automatically. It&#39;s a bit dirty, but it gets the job done.&lt;/p&gt;
&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;TestSuite&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;unregister&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;suite&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token variable&quot;&gt;@suites&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;delete&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;suite&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Let&#39;s define a test helper that we&#39;ll use everywhere so that we don&#39;t forget to unregister any inner test suites:&lt;/p&gt;
&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token function&quot;&gt;define_suite&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;block&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  suite &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Class&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;TestSuite&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;amp;&lt;/span&gt;block&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  TestSuite&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;unregister&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;suite&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  suite
&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Example usage:&lt;/p&gt;
&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ReportingTests&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; TestSuite
  &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token function&quot;&gt;test_statistics&lt;/span&gt;&lt;/span&gt;
    suite &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; define_suite &lt;span class=&quot;token keyword&quot;&gt;do&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token function&quot;&gt;test_equality&lt;/span&gt;&lt;/span&gt;
        assert_equal&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;

      &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token function&quot;&gt;test_the_truth&lt;/span&gt;&lt;/span&gt;
        assert&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;

    output &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;StringIO&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt;
    report &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; suite&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;run&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;output&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    assert_equal&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; report&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;runs&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    assert_equal&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; report&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;failures&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    assert&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;output&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;string&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;include&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;2 runs, 1 failures&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
           &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Report does not include statistics&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After we replace all previous inner test suite instantiations with our helper, we can replace &lt;code&gt;AssertionTests.new.run&lt;/code&gt; and &lt;code&gt;ReportingTests.new.run&lt;/code&gt; with a single &lt;code&gt;TestSuite.run&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;(Commit &lt;a href=&quot;https://github.com/crackofdusk/raisin/commit/6fa98df2f0b6a560425780899aeedc0863328594&quot;&gt;6fa98df&lt;/a&gt;)&lt;/p&gt;
&lt;h2&gt;Auto-runner&lt;/h2&gt;
&lt;p&gt;It would be cool to not have to invoke &lt;code&gt;TestSuite.run&lt;/code&gt; at all. With Minitest, you can require &lt;code&gt;mintiest/autorun&lt;/code&gt; and executing the script automatically runs the tests within. Let&#39;s implement that!&lt;/p&gt;
&lt;p&gt;The &lt;a href=&quot;http://ruby-doc.org/core-2.3.1/Kernel.html#method-i-at_exit&quot;&gt;&lt;code&gt;at_exit&lt;/code&gt;&lt;/a&gt; method takes a block that will get executed when the program exits. The end of the program is a good place to run tests, because it guarantees that all the production and test code has been loaded.&lt;/p&gt;
&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;require_relative &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&#39;./raisin&#39;&lt;/span&gt;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;module&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Raisin&lt;/span&gt;
  &lt;span class=&quot;token variable&quot;&gt;@@at_exit_registered&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;||=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;autorun&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;unless&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;@@at_exit_registered&lt;/span&gt;
      at_exit &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; TestSuite&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;run &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;token variable&quot;&gt;@@at_exit_registered&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;

Raisin&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;autorun&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In the code above, we make sure that we only register the callback only once in case that file is &lt;code&gt;require&lt;/code&gt;d multiple times. Let&#39;s remove that &lt;code&gt;TestSuite.run&lt;/code&gt; from the bottom of our test file and replace the &lt;code&gt;require_relative &#39;./raisin&#39;&lt;/code&gt; by &lt;code&gt;require_relative &#39;./autorun&#39;&lt;/code&gt;. Run it:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;$ ruby test.rb
&lt;span class=&quot;token punctuation&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;..&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;..&lt;/span&gt;

&lt;span class=&quot;token number&quot;&gt;6&lt;/span&gt; runs, &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt; failures
&lt;span class=&quot;token punctuation&quot;&gt;..&lt;/span&gt;

&lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt; runs, &lt;span class=&quot;token number&quot;&gt;0&lt;/span&gt; failures&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It works!&lt;/p&gt;
&lt;p&gt;(Commit &lt;a href=&quot;https://github.com/crackofdusk/raisin/commit/2a53359a156435f3623018fb53a2c4b29c273fff&quot;&gt;2a53359&lt;/a&gt;)&lt;/p&gt;
&lt;h2&gt;Better reports&lt;/h2&gt;
&lt;p&gt;Our test output is useful, but it could be better. We don&#39;t want it broken down by class, we want to combine the progress reports and print a grand summary at the end. We would also like failures to be reported where the assertion was made and not at the line of the library internals where the exception was raised.&lt;/p&gt;
&lt;p&gt;First, we change &lt;code&gt;TestSuite#run&lt;/code&gt; to take the report as argument, &lt;code&gt;TestSuite.run&lt;/code&gt; to pass it in and we update our tests:&lt;/p&gt;
&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;TestSuite&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;run&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;io &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$stdout&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token variable&quot;&gt;@suites&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;each&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;suite&lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;
      suite&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;run&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Report&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;io&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token function&quot;&gt;run&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;report&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;# ...&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;token comment&quot;&gt;# in the tests&lt;/span&gt;
suite &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; define_suite &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token comment&quot;&gt;#.... }&lt;/span&gt;
output &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;StringIO&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt;
suite&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;run&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Report&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;output&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then we make only one report and pass to the different test suites. In the end we print the summary.&lt;/p&gt;
&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;TestSuite&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;run&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;io &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$stdout&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    report &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Report&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;io&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;token variable&quot;&gt;@suites&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;each&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;suite&lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;
      suite&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;run&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;report&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;

    report&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;summarize
  &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token function&quot;&gt;run&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;report&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    test_names &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; public_methods&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;grep&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token regex-literal&quot;&gt;&lt;span class=&quot;token regex&quot;&gt;/^test_/&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

    test_names&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;each&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;test&lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;
      result &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; TestResult&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;from &lt;span class=&quot;token keyword&quot;&gt;do&lt;/span&gt;
        setup
        send&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;test&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
        teardown
      &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;
      report&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;add_result&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;result&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;
    report
  &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Since the &lt;code&gt;run&lt;/code&gt; instance method no longer creates a report by itself and it does not invoke &lt;code&gt;report.summarize&lt;/code&gt; we need to add those steps to the tests to make them pass.&lt;/p&gt;
&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;ReportingTests&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&amp;lt;&lt;/span&gt; TestSuite
  &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token function&quot;&gt;test_statistics&lt;/span&gt;&lt;/span&gt;
    suite &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; define_suite &lt;span class=&quot;token keyword&quot;&gt;do&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token function&quot;&gt;test_equality&lt;/span&gt;&lt;/span&gt;
        assert_equal&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;

      &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token function&quot;&gt;test_the_truth&lt;/span&gt;&lt;/span&gt;
        assert&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;

    output &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;StringIO&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt;
    report &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; suite&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;run&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token class-name&quot;&gt;Report&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;output&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    report&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;summarize

    assert_equal&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; report&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;runs&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    assert_equal&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token number&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; report&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;failures&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    assert&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;output&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;string&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;include&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;2 runs, 1 failures&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
           &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Report does not include statistics&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token function&quot;&gt;test_summary&lt;/span&gt;&lt;/span&gt;
    suite &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; define_suite &lt;span class=&quot;token keyword&quot;&gt;do&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token function&quot;&gt;test_1&lt;/span&gt;&lt;/span&gt;
        assert&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;failure1&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;

      &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token function&quot;&gt;test_2&lt;/span&gt;&lt;/span&gt;
        assert&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;failure2&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;

    output &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;StringIO&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt;
    report &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Report&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;output&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    suite&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;run&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;report&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    report&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;summarize

    assert&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;output&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;string&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;include&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;failure1&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
           &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Report does not include error details&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    assert&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;output&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;string&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;include&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;failure2&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
           &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;Report does not include error details&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The test setup is a little heavy, but I like it more than the alternatives I can think of. We could declare &lt;code&gt;TestResult&lt;/code&gt;s instead of a test suite and feed those to the report (with &lt;code&gt;report.add_result(result)&lt;/code&gt;). That involves too many library internals. We also cannot use the &lt;code&gt;TestSuite.run&lt;/code&gt; facade, because at this point we&#39;re already in code executed by it. That would effectively run a loop until we reach the call stack depth limit. Besides, we&#39;re already verifying that reports are wired up correctly through visual inspection: running our own test suite produces output. It&#39;s the same with registering test suites through inheritance: because we always make a test fail first, we can tell something is wrong if we don&#39;t see the failure.&lt;/p&gt;
&lt;p&gt;(Commit &lt;a href=&quot;https://github.com/crackofdusk/raisin/commit/9f45081c65862dbd1d066319ea6b92f332ec8c91&quot;&gt;9f45081&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;Let&#39;s also exclude &lt;code&gt;raisin&lt;/code&gt; internals from the backtrace. First we&#39;ll organize the project files like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ tree
.
├── lib
│   ├── raisin
│   │   └── autorun.rb
│   └── raisin.rb
├── README.md
└── test
    └── raisin.rb
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Don&#39;t forget to change the &lt;code&gt;require_relative&lt;/code&gt; paths. Also note &lt;code&gt;test.rb&lt;/code&gt; is now &lt;code&gt;test/raisin.rb&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;(Commit &lt;a href=&quot;https://github.com/crackofdusk/raisin/commit/17454c3ae89b3aba180808d6d107c85dbc33cc0f&quot;&gt;17454c3&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;Then we exclude backtrace lines referring to &lt;code&gt;lib/raisin&lt;/code&gt;.&lt;/p&gt;
&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Report&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token function&quot;&gt;summarize&lt;/span&gt;&lt;/span&gt;
    io&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;puts
    &lt;span class=&quot;token variable&quot;&gt;@errors&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;each&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;failure&lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;
      io&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;puts
      io&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;puts failure&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;message
      io&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;puts filter&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;failure&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;backtrace&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
      io&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;puts
    &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;
    io&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;puts
    io&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;puts &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;token content&quot;&gt;runs&lt;/span&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt; runs, &lt;/span&gt;&lt;span class=&quot;token interpolation&quot;&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;token content&quot;&gt;failures&lt;/span&gt;&lt;span class=&quot;token delimiter punctuation&quot;&gt;}&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt; failures&quot;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token function&quot;&gt;filter&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;backtrace&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    backtrace&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;rejct &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;line&lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; line &lt;span class=&quot;token operator&quot;&gt;=~&lt;/span&gt; &lt;span class=&quot;token regex-literal&quot;&gt;&lt;span class=&quot;token regex&quot;&gt;/lib\/raisin/&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;(Commit &lt;a href=&quot;https://github.com/crackofdusk/raisin/commit/088fbd25768c6b0b666db4ece5cb5d22457e6bc4&quot;&gt;088fbd2&lt;/a&gt;)&lt;/p&gt;
&lt;h2&gt;Random execution order&lt;/h2&gt;
&lt;p&gt;A nice feature for a test framework is the execution of test cases in a random order. This helps improve the quality of your tests by detecting dependencies. The tests should pass no matter the execution order. Another aspect of this is that if a certain ordering makes the tests fail, the programmer should be able to reproduce that particular run. To make the process deterministic we&#39;ll take a random seed as an optional command line argument and generate a default if none is given.&lt;/p&gt;
&lt;p&gt;First, we introduce a new entry point:&lt;/p&gt;
&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;module&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Raisin&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;run&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;args&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    TestSuite&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;run
  &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We modify &lt;code&gt;Raisin.autorun&lt;/code&gt; to call it with the command-line arguments:&lt;/p&gt;
&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;module&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Raisin&lt;/span&gt;
  &lt;span class=&quot;token variable&quot;&gt;@@at_exit_registered&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;||=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;autorun&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;unless&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;@@at_exit_registered&lt;/span&gt;
      at_exit &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; Raisin&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;run&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token constant&quot;&gt;ARGV&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
      &lt;span class=&quot;token variable&quot;&gt;@@at_exit_registered&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We parse the arguments and pass them to the test suite runner.&lt;/p&gt;
&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&#39;optparse&#39;&lt;/span&gt;&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;module&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Raisin&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;run&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;args&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    options &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; RunOptions&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;parse&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;args&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    TestSuite&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;run&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;options&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;RunOptions&lt;/span&gt;
  attr_reader &lt;span class=&quot;token symbol&quot;&gt;:seed&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;parse&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;arguments &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    program_options &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;

    parser &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;OptionParser&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;options&lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;
      options&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;on&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&#39;-h&#39;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&#39;--help&#39;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&#39;Display this help&#39;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;do&lt;/span&gt;
        puts options
        exit
      &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;

      options&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;on&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&#39;--seed SEED&#39;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;Integer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&#39;Set random seed&#39;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;value&lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;
        program_options&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token symbol&quot;&gt;:seed&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; value&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;to_i
      &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;

    parser&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;parse&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;arguments&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;program_options&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;fetch&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token symbol&quot;&gt;:seed&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; Random&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;new_seed&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token function&quot;&gt;initialize&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;seed&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token variable&quot;&gt;@seed&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; seed
  &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;There are a few things going on here. &lt;a href=&quot;http://ruby-doc.org/stdlib-2.3.1/libdoc/optparse/rdoc/OptionParser.html&quot;&gt;&lt;code&gt;OptionParser&lt;/code&gt;&lt;/a&gt; is from the standard library and does the heavy lifting of parsing arguments and pretty printing help. &lt;code&gt;program_options.fetch(:seed, Random.new_seed)&lt;/code&gt; makes sure we always know what the random seed is.&lt;/p&gt;
&lt;p&gt;Then we shuffle the test order using a random number generator seeded with that seed.&lt;/p&gt;
&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;TestSuite&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;run&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;io &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$stdout&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; options&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    report &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Report&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;io&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; options&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

    &lt;span class=&quot;token variable&quot;&gt;@suites&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;each&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;suite&lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;
      suite&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;run&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;report&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; options&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;# ...&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token function&quot;&gt;run&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;report&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; options&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;# ...&lt;/span&gt;

    test_names&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;shuffle&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token symbol&quot;&gt;random&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Random&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;options&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;seed&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;each&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;test&lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt;
      &lt;span class=&quot;token comment&quot;&gt;# ...&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;

    &lt;span class=&quot;token comment&quot;&gt;# ...&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We could also &lt;a href=&quot;https://ruby-doc.org/core-2.3.1/Random.html#method-c-srand&quot;&gt;&lt;code&gt;srand&lt;/code&gt;&lt;/a&gt; at the beginning of the program. However, that could interfere with the code being tested if that also contains calls to &lt;code&gt;srand&lt;/code&gt;. Furthermore, passing in the seed makes the code more explicit and testing it easier.&lt;/p&gt;
&lt;p&gt;Finally, we let the user know how to repeat that specific test order:&lt;/p&gt;
&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;RunOptions&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token function&quot;&gt;invocation_command&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&#39;ruby&#39;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$PROGRAM_NAME&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&#39;--seed&#39;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; seed&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;join&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&quot; &quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Report&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token function&quot;&gt;initialize&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;io&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; options&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;# ...&lt;/span&gt;
    &lt;span class=&quot;token variable&quot;&gt;@options&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; options
  &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token function&quot;&gt;summarize&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token comment&quot;&gt;# ...&lt;/span&gt;
    io&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;puts
    io&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;puts &lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&#39;Rerun the tests in the same order with:&#39;&lt;/span&gt;&lt;/span&gt;
    io&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;puts options&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;invocation_command
  &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt;

  attr_reader &lt;span class=&quot;token symbol&quot;&gt;:options&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Testing the feature can be as simple as finding a seed that will run tests in a specific order and checking that order:&lt;/p&gt;
&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token function&quot;&gt;test_order&lt;/span&gt;&lt;/span&gt;
  suite &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; define_suite &lt;span class=&quot;token keyword&quot;&gt;do&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token function&quot;&gt;test_1&lt;/span&gt;&lt;/span&gt;
      assert&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token boolean&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;

    &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token function&quot;&gt;test_2&lt;/span&gt;&lt;/span&gt;
      assert&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;

  output &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;StringIO&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt;
  options &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; RunOptions&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;parse&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;%w[--seed 2]&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  report &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;Report&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;output&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; options&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  suite&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;run&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;report&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; options&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

  assert_equal&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string-literal&quot;&gt;&lt;span class=&quot;token string&quot;&gt;&#39;.F&#39;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; output&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;string&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The natural output order here would be &amp;quot;F.&amp;quot; and we check it is reversed with a specific seed. Removing the seed (&lt;code&gt;options = RunOptions.parse([])&lt;/code&gt;) will sometimes fail the test, but with a seed of 2 it always passes.&lt;/p&gt;
&lt;p&gt;(Commit &lt;a href=&quot;https://github.com/crackofdusk/raisin/commit/991a00e82bbc26d0e8f679c5eb77a16a0efd9624&quot;&gt;991a00e&lt;/a&gt;)&lt;/p&gt;
&lt;h2&gt;Cleanup&lt;/h2&gt;
&lt;p&gt;Before we wrap up, let&#39;s put all classes and modules in the &lt;code&gt;Raisin&lt;/code&gt; module. That way we don&#39;t pollute the global namespace. The tests need updating as well to prefix everything in our library with the top-level namespace.&lt;/p&gt;
&lt;p&gt;(Commit &lt;a href=&quot;https://github.com/crackofdusk/raisin/commit/a5b7220ffcddcdb1820851b38c420090fd1d35ba&quot;&gt;a5b7220&lt;/a&gt;)&lt;/p&gt;
&lt;h2&gt;Final words&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;cloc&lt;/code&gt; utility counts 151 lines of Ruby in the &lt;code&gt;lib/&lt;/code&gt; directory and 96 lines in &lt;code&gt;test/&lt;/code&gt;. That is not a lot for a fully functional test library.&lt;/p&gt;
&lt;p&gt;We&#39;re missing a few useful assertions, like a test that two floating point numbers are very close (we cannot use &lt;code&gt;assert_equal&lt;/code&gt; because of machine precision). This and other assertions can be easily built on top of the &lt;code&gt;assert&lt;/code&gt; primitive.&lt;/p&gt;
&lt;p&gt;I also left out test doubles on purpose. I may revisit the project to add implement them later.&lt;/p&gt;
&lt;p&gt;Another thing to implement is a more helpful comparison of expected and actual input for &lt;code&gt;assert_equal&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;All in all, writing this library satisfied my own curiosity about self-tested code. I hope the post inspired you to write your own toy test library!&lt;/p&gt;
&lt;p&gt;See the &lt;a href=&quot;https://github.com/crackofdusk/raisin&quot;&gt;final code&lt;/a&gt;.&lt;/p&gt;
</content>
    
  </entry>
  
    
  
  <entry>
    <title>Elm Single Page Application Setup</title>
    <link href="https://dimiterpetrov.com/blog/elm-single-page-application-setup/"/>
    <updated>2016-08-03T00:00:00Z</updated>
    <id>https://dimiterpetrov.com/blog/elm-single-page-application-setup/</id>
    
    <content type="html">&lt;p&gt;&lt;em&gt;Update (2018):&lt;/em&gt; While the ideas in this post are still valid &lt;a href=&quot;https://github.com/wking-io/elm-live&quot;&gt;elm-live&lt;/a&gt; will get you started a lot faster. Also check out the &lt;a href=&quot;https://guide.elm-lang.org/optimization/asset_size.html&quot;&gt;Elm guide section on minification&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/elm-lang/elm-reactor&quot;&gt;elm-reactor&lt;/a&gt; is good for iterating quickly on self-contained programs. You run &lt;code&gt;elm-reactor&lt;/code&gt;, you navigate to the source file that contains the entry point to your program and you are up and running. &lt;code&gt;elm-reactor&lt;/code&gt; automatically compiles your program and embeds it in an HTML page. You refresh that page when you want to try out modifications to your program or read the compiler errors.&lt;/p&gt;
&lt;p&gt;When you start a bigger project you may need to customize that bootstrap page to, say, include external CSS or JavaScript. The Elm 0.17 reactor gives you no control over that. You need to compile your program to a JavaScript file with &lt;code&gt;elm-make&lt;/code&gt; using the &lt;code&gt;--output&lt;/code&gt; flag and then reference that in your custom HTML. Now there is an extra step in the development workflow.&lt;/p&gt;
&lt;p&gt;When I started developing a single page application in Elm, I decided to make this workflow as nice as possible for myself by automating the build process. I came up with something that works well for me and that I want to share. Before we get into the specifics, however, let&#39;s see what needs my tooling solves.&lt;/p&gt;
&lt;h2&gt;Requirements and assumptions&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Use &lt;code&gt;make&lt;/code&gt;&lt;/strong&gt;. This is a matter of personal preference. You can find other resources if using Gulp or Webpack is more suitable for your project.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Static, self-sufficient build artifact&lt;/strong&gt;: everything must be contained within a single directory that can be served by a production server or a &lt;abbr title=&quot;Content Delivery Network&quot;&gt;CDN&lt;/abbr&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Compression&lt;/strong&gt;: minifying the JavaScript output of the Elm compiler is an easy win for loading speed.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;External CSS&lt;/strong&gt;: &lt;a href=&quot;https://github.com/rtfeldman/elm-css&quot;&gt;elm-css&lt;/a&gt; is an interesting way to handle styling, but the warning about it being a beta release helped me decide to stick to what I know for now. This should also be minified.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;External JS&lt;/strong&gt;: some things are currently not doable directly in Elm, like programmatically setting focus to a form field. &lt;a href=&quot;http://guide.elm-lang.org/interop/javascript.html&quot;&gt;JavaScript interoperability&lt;/a&gt; solves this.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Images and web fonts&lt;/strong&gt;: no processing is necessary for these assets, but they need to be copied to the output directory.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Development/production build parity&lt;/strong&gt;: since static files are going to be served in production, you might as well serve the exact same files in development.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Automatic rebuild on file change&lt;/strong&gt; to reduce the number of manual steps to what we have with &lt;code&gt;elm-reactor&lt;/code&gt;. The Elm compiler is fast after the first build since it keeps unchanged modules in cache, therefore this is relatively fast. The minification phase is a potential bottleneck depending on the app size.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Automatic browser refresh&lt;/strong&gt;: switching to your browser window and refreshing the page are two extra steps that you needn&#39;t take.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Node.js is installed&lt;/strong&gt;. The easiest way to install Elm is through &lt;code&gt;npm&lt;/code&gt; and there are a lot of &lt;code&gt;npm&lt;/code&gt; packages that address our build needs, making Node.js a good starting point.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Bootstrapping Elm&lt;/h2&gt;
&lt;p&gt;In a new project directory, create the &lt;code&gt;npm&lt;/code&gt; configuration file &lt;code&gt;package.json&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;npm&lt;/span&gt; init&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Install Elm as an &lt;code&gt;npm&lt;/code&gt; package.&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;&lt;span class=&quot;token function&quot;&gt;npm&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;install&lt;/span&gt; --save-dev elm&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I usually create the &lt;code&gt;Makefile&lt;/code&gt; at this point and add a single target: &lt;code&gt;setup&lt;/code&gt;. That is the convention I use for installing dependencies in projects that use &lt;code&gt;make&lt;/code&gt;.&lt;/p&gt;
&lt;pre class=&quot;language-makefile&quot;&gt;&lt;code class=&quot;language-makefile&quot;&gt;&lt;span class=&quot;token target symbol&quot;&gt;setup&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
	npm install&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Make sure to document in the project&#39;s &lt;code&gt;README&lt;/code&gt; that the project is set up with &lt;code&gt;make setup&lt;/code&gt; (or just &lt;code&gt;npm install&lt;/code&gt; if you didn&#39;t add a &lt;code&gt;setup&lt;/code&gt; target in the &lt;code&gt;Makefile&lt;/code&gt;).&lt;/p&gt;
&lt;h2&gt;Hello, world!&lt;/h2&gt;
&lt;p&gt;Create a &lt;code&gt;src/&lt;/code&gt; directory to hold your Elm program. Write a simple main module in &lt;code&gt;src/Main.elm&lt;/code&gt; for testing:&lt;/p&gt;
&lt;pre class=&quot;language-elm&quot;&gt;&lt;code class=&quot;language-elm&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;module&lt;/span&gt; &lt;span class=&quot;token constant&quot;&gt;Main&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;exposing&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token hvariable&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token import-statement&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; Html&lt;/span&gt;

&lt;span class=&quot;token hvariable&quot;&gt;main&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;
    &lt;span class=&quot;token hvariable&quot;&gt;Html.text&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Hello, world!&quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Compile it with:&lt;/p&gt;
&lt;pre class=&quot;language-bash&quot;&gt;&lt;code class=&quot;language-bash&quot;&gt;./node_modules/.bin/elm-make src/Main.elm&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This will ask to install some Elm packages before it can compile. It will automatically create the default &lt;code&gt;elm-package.json&lt;/code&gt;. Update it to point to &lt;code&gt;src/&lt;/code&gt; as a source directory. Otherwise, once you start adding new modules, &lt;code&gt;elm make&lt;/code&gt; will complain about not finding them. It should look something like this (the important bit is &lt;code&gt;&amp;quot;source-directories&amp;quot;&lt;/code&gt;):&lt;/p&gt;
&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;version&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;1.0.0&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;summary&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Project summary&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;repository&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;https://github.com/user/project.git&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;license&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;BSD3&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;source-directories&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;
        &lt;span class=&quot;token string&quot;&gt;&quot;src/&quot;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;exposed-modules&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;dependencies&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;&quot;elm-lang/core&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;4.0.3 &amp;lt;= v &amp;lt; 5.0.0&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;token property&quot;&gt;&quot;elm-lang/html&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;1.1.0 &amp;lt;= v &amp;lt; 2.0.0&quot;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;elm-version&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;0.17.1 &amp;lt;= v &amp;lt; 0.18.0&quot;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now we can add a compilation step to the &lt;code&gt;Makefile&lt;/code&gt;.&lt;/p&gt;
&lt;pre class=&quot;language-makefile&quot;&gt;&lt;code class=&quot;language-makefile&quot;&gt;BIN &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; ./node_modules/.bin
SRC &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; src
BUILD &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; build

&lt;span class=&quot;token target symbol&quot;&gt;build&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; build-directory js

&lt;span class=&quot;token target symbol&quot;&gt;build-directory&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
	mkdir -p &lt;span class=&quot;token variable&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;BUILD&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token target symbol&quot;&gt;js&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
	&lt;span class=&quot;token variable&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;BIN&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;/elm make src/Main.elm --output &lt;span class=&quot;token variable&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;BUILD&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;/app.js&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I declared some variables at the top, because we are going to use these paths a lot. Now running &lt;code&gt;make&lt;/code&gt; compiles our program and writes it to &lt;code&gt;build/app.js&lt;/code&gt;. This is everything we need for the Elm part even as the project grows. The compiler will bundle all imported modules.&lt;/p&gt;
&lt;p&gt;Let&#39;s add our custom &lt;code&gt;src/index.html&lt;/code&gt; that imports &lt;code&gt;app.js&lt;/code&gt;.&lt;/p&gt;
&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;token doctype&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;!&lt;/span&gt;&lt;span class=&quot;token doctype-tag&quot;&gt;DOCTYPE&lt;/span&gt; &lt;span class=&quot;token name&quot;&gt;html&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;html&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;head&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;meta&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;charset&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;utf-8&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;title&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;Elm app&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;title&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;head&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;body&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;body&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;script&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;src&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;app.js&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token script&quot;&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;script&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
  &lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;script&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token script&quot;&gt;&lt;span class=&quot;token language-javascript&quot;&gt;Elm&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;Main&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;fullscreen&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;script&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;/&lt;/span&gt;html&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The corresponding &lt;code&gt;Makefile&lt;/code&gt; section simply copies that file to the &lt;code&gt;build&lt;/code&gt; directory.&lt;/p&gt;
&lt;pre class=&quot;language-makefile&quot;&gt;&lt;code class=&quot;language-makefile&quot;&gt;&lt;span class=&quot;token target symbol&quot;&gt;html&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
	cp &lt;span class=&quot;token variable&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;SRC&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;/index.html &lt;span class=&quot;token variable&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;BUILD&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;/index.html&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When adding new build steps, don&#39;t forget to add them as dependencies to the &lt;code&gt;build&lt;/code&gt; target in the &lt;code&gt;Makefile&lt;/code&gt;. Right now, it should look like this:&lt;/p&gt;
&lt;pre class=&quot;language-makefile&quot;&gt;&lt;code class=&quot;language-makefile&quot;&gt;&lt;span class=&quot;token target symbol&quot;&gt;build&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; build-directory js html&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Automatic rebuilds on file changes&lt;/h2&gt;
&lt;p&gt;The &lt;a href=&quot;https://www.npmjs.com/package/chokidar-cli&quot;&gt;&lt;code&gt;chokidar-cli&lt;/code&gt;&lt;/a&gt; package provides a command-line utility that watches files for changes and runs a command in reaction. In this case, we want to run &lt;code&gt;make&lt;/code&gt; when files in the &lt;code&gt;src/&lt;/code&gt; or &lt;code&gt;assets/&lt;/code&gt; directories are modified. &lt;code&gt;src/&lt;/code&gt; contains the source code and all the stylesheets, web fonts and images are stored in &lt;code&gt;assets/&lt;/code&gt;.&lt;/p&gt;
&lt;pre class=&quot;language-makefile&quot;&gt;&lt;code class=&quot;language-makefile&quot;&gt;&lt;span class=&quot;token target symbol&quot;&gt;watch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
	&lt;span class=&quot;token operator&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;BIN&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;/chokidar &lt;span class=&quot;token string&quot;&gt;&quot;$(SRC)&quot;&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;assets&quot;&lt;/span&gt; -c &lt;span class=&quot;token string&quot;&gt;&quot;make&quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Cool! We can use our &lt;code&gt;elm-reactor&lt;/code&gt;-like workflow by running &lt;code&gt;make watch&lt;/code&gt; and opening &lt;code&gt;build/index.html&lt;/code&gt;. Whenever we save a file, we can see the changes by refreshing the browser page. One difference is that the compiler errors are displayed in the terminal instead of the browser.&lt;/p&gt;
&lt;p&gt;Now is a good time to document the effects of &lt;code&gt;make&lt;/code&gt; and &lt;code&gt;make watch&lt;/code&gt; in your &lt;code&gt;README&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;Live reloading&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/bhauman/lein-figwheel&quot;&gt;Figwheel&lt;/a&gt; provides a wonderful development experience for ClojureScript projects. We can get close to it thanks to &lt;a href=&quot;https://www.browsersync.io/&quot;&gt;BrowserSync&lt;/a&gt;.&lt;/p&gt;
&lt;pre class=&quot;language-makefile&quot;&gt;&lt;code class=&quot;language-makefile&quot;&gt;&lt;span class=&quot;token target symbol&quot;&gt;build&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; build-directory html js css fonts images
	&lt;span class=&quot;token variable&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;BIN&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;/browser-sync reload

&lt;span class=&quot;token target symbol&quot;&gt;browser-sync&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
	&lt;span class=&quot;token variable&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;BIN&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;/browser-sync start --server &lt;span class=&quot;token variable&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;BUILD&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Running &lt;code&gt;make browser-sync&lt;/code&gt; will open the app in your browser and automatically refresh it after each build (note that we added the command &lt;code&gt;browser-sync reload&lt;/code&gt; to the &lt;code&gt;build&lt;/code&gt; target). What&#39;s more, if you access the same URL from another browser, even other devices, all instances will be refreshed without manual intervention.&lt;/p&gt;
&lt;p&gt;Depending on the way your application derives its state you may lose state between reloads, but that is already the case with manual reloads. How you can achieve seamless reloads is a topic for another time.&lt;/p&gt;
&lt;h2&gt;Minification&lt;/h2&gt;
&lt;p&gt;The Google Closure compiler requires Java, so I opted for &lt;a href=&quot;https://www.npmjs.com/package/uglify-js&quot;&gt;UglifyJs&lt;/a&gt;, which gives good results. I enabled name mangling and compression. This reduces the file size significantly (from 164Kb down to 64Kb for the Hello World example). Gzip reduces the size even further (down to 19Kb), but gzipping can be done by the web server.&lt;/p&gt;
&lt;pre class=&quot;language-makefile&quot;&gt;&lt;code class=&quot;language-makefile&quot;&gt;&lt;span class=&quot;token target symbol&quot;&gt;js&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
	&lt;span class=&quot;token variable&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;BIN&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;/elm make &lt;span class=&quot;token variable&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;SRC&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;/Main.elm \
		--output &lt;span class=&quot;token variable&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;BUILD&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;/app.js
	&lt;span class=&quot;token variable&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;BIN&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;/uglifyjs --compress --mangle \
		--output &lt;span class=&quot;token variable&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;BUILD&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;/app.min.js \
		&lt;span class=&quot;token variable&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;BUILD&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;/app.js 2&gt; /dev/null
	mv &lt;span class=&quot;token variable&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;BUILD&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;/app.min.js &lt;span class=&quot;token variable&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;BUILD&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;/app.js&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I redirect the UglifyJS error stream to /dev/null, because it just contains warnings about the minification of things in the Elm standard library: &amp;quot;Side effects in initialization of unused variable&amp;quot;, &amp;quot;Dropping unused variable&amp;quot;, &amp;quot;Dropping unused function&amp;quot;. Ignoring warnings is generally not a good idea, but in this case:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The dead code elimination phase doesn&#39;t drop any of the unused variables with &amp;quot;side effects in initialization&amp;quot;.&lt;/li&gt;
&lt;li&gt;Minification with &lt;code&gt;--compress --mangle&lt;/code&gt; doesn&#39;t impair the program. Maybe more advanced options do.&lt;/li&gt;
&lt;li&gt;Even if something went wrong, we would notice since we are using the production build in development.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Stylesheets&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://www.npmjs.com/package/clean-css&quot;&gt;&lt;code&gt;clean-css&lt;/code&gt;&lt;/a&gt; works great for my purposes. It takes your concatenated CSS files as input and creates a minified stylesheet. If you need Sass, &lt;a href=&quot;https://www.npmjs.com/package/node-sass&quot;&gt;&lt;code&gt;node-sass&lt;/code&gt;&lt;/a&gt; has a command line interface that can be used in a similar way.&lt;/p&gt;
&lt;pre class=&quot;language-makefile&quot;&gt;&lt;code class=&quot;language-makefile&quot;&gt;CSS &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; assets/stylesheets

&lt;span class=&quot;token target symbol&quot;&gt;css&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
	cat &lt;span class=&quot;token variable&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;CSS&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;/*.css &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; \
		&lt;span class=&quot;token variable&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;BIN&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;/cleancss --output &lt;span class=&quot;token variable&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;BUILD&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;/style.css&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Import the stylesheet in the &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; section of &lt;code&gt;src/index.html&lt;/code&gt;:&lt;/p&gt;
&lt;pre class=&quot;language-html&quot;&gt;&lt;code class=&quot;language-html&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token tag&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;&amp;lt;&lt;/span&gt;link&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;rel&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;stylesheet&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;text/css&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token attr-name&quot;&gt;href&lt;/span&gt;&lt;span class=&quot;token attr-value&quot;&gt;&lt;span class=&quot;token punctuation attr-equals&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;style.css&lt;span class=&quot;token punctuation&quot;&gt;&quot;&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;/&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Final result&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;package.json&lt;/code&gt; configuration file looks something like this:&lt;/p&gt;
&lt;pre class=&quot;language-json&quot;&gt;&lt;code class=&quot;language-json&quot;&gt;&lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;elm-spa&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;version&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;1.0.0&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;private&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token boolean&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;description&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Elm single page application&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;dependencies&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;devDependencies&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;browser-sync&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;^2.14.0&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;chokidar-cli&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;^1.2.0&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;clean-css&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;^3.4.18&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;elm&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;^0.17.1&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;uglify-js&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;^2.7.0&quot;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;scripts&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;token property&quot;&gt;&quot;test&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;echo \&quot;Error: no test specified\&quot; &amp;amp;&amp;amp; exit 1&quot;&lt;/span&gt;
  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;author&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;token property&quot;&gt;&quot;license&quot;&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;UNLICENSED&quot;&lt;/span&gt;
&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;build&lt;/code&gt; directory contains the following after I run &lt;code&gt;make&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ tree -L 1 build
build
├── app.js
├── fonts
├── images
├── index.html
├── setup.js
└── style.css

2 directories, 4 files
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;setup.js&lt;/code&gt; contains the JavaScript code that embeds the Elm program and sets up &lt;a href=&quot;http://guide.elm-lang.org/interop/javascript.html&quot;&gt;ports&lt;/a&gt;. In the final &lt;code&gt;Makefile&lt;/code&gt; below you can see that I also compress &lt;code&gt;setup.js&lt;/code&gt;.&lt;/p&gt;
&lt;pre class=&quot;language-makefile&quot;&gt;&lt;code class=&quot;language-makefile&quot;&gt;BIN &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; ./node_modules/.bin
SRC &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; src
BUILD &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; build
CSS &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; assets/stylesheets
FONTS &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; assets/fonts
IMAGES &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; assets/images
HTML &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; src/index.html

&lt;span class=&quot;token target symbol&quot;&gt;build&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; build-directory html js css fonts images

&lt;span class=&quot;token target symbol&quot;&gt;build-directory&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
	mkdir -p &lt;span class=&quot;token variable&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;BUILD&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token target symbol&quot;&gt;html&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
	cp &lt;span class=&quot;token variable&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;HTML&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;BUILD&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;/index.html

&lt;span class=&quot;token target symbol&quot;&gt;js&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
	&lt;span class=&quot;token variable&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;BIN&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;/elm make &lt;span class=&quot;token variable&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;SRC&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;/Main.elm \
		--output &lt;span class=&quot;token variable&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;BUILD&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;/app.js
	&lt;span class=&quot;token variable&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;BIN&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;/uglifyjs --compress --mangle \
		--output &lt;span class=&quot;token variable&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;BUILD&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;/app.min.js \
		&lt;span class=&quot;token variable&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;BUILD&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;/app.js 2&gt; /dev/null
	mv &lt;span class=&quot;token variable&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;BUILD&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;/app.min.js &lt;span class=&quot;token variable&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;BUILD&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;/app.js
	&lt;span class=&quot;token variable&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;BIN&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;/uglifyjs --compress --mangle \
		--output &lt;span class=&quot;token variable&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;BUILD&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;/setup.js \
		&lt;span class=&quot;token variable&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;SRC&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;/setup.js 2&gt; /dev/null

&lt;span class=&quot;token target symbol&quot;&gt;css&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
	cat &lt;span class=&quot;token variable&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;CSS&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;/*.css &lt;span class=&quot;token operator&quot;&gt;|&lt;/span&gt; \
		&lt;span class=&quot;token variable&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;BIN&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;/cleancss --output &lt;span class=&quot;token variable&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;BUILD&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;/style.css

&lt;span class=&quot;token target symbol&quot;&gt;fonts&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
	cp -r &lt;span class=&quot;token variable&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;FONTS&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;BUILD&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token target symbol&quot;&gt;images&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
	cp -r &lt;span class=&quot;token variable&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;IMAGES&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;BUILD&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token target symbol&quot;&gt;watch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
	&lt;span class=&quot;token variable&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;BIN&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;/chokidar &lt;span class=&quot;token string&quot;&gt;&quot;$(SRC)&quot;&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;assets&quot;&lt;/span&gt; -c &lt;span class=&quot;token string&quot;&gt;&quot;make&quot;&lt;/span&gt;

&lt;span class=&quot;token target symbol&quot;&gt;browser-sync&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
	&lt;span class=&quot;token variable&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;BIN&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;/browser-sync start \
		--server &lt;span class=&quot;token variable&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;BUILD&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; --files &lt;span class=&quot;token variable&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;BUILD&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;token target symbol&quot;&gt;setup&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
	npm install&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Make sure to use tabs for indentation or &lt;code&gt;make&lt;/code&gt; will fail with a cryptic error message.&lt;/p&gt;
&lt;h2&gt;Possible improvements&lt;/h2&gt;
&lt;h3&gt;Deployment automation&lt;/h3&gt;
&lt;p&gt;It would be good to be able to deploy the app in a single step by running&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;make deploy
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This depends on where you deploy, so I left it out.&lt;/p&gt;
&lt;h3&gt;Cleaner &lt;code&gt;make watch&lt;/code&gt; logs&lt;/h3&gt;
&lt;p&gt;By default &lt;code&gt;make&lt;/code&gt; prints a command before executing it. It also prints &amp;quot;Entering directory&amp;quot; and &amp;quot;Leaving directory&amp;quot; messages. All of this creates a lot of noise and we only care about compiler errors. There are &lt;a href=&quot;https://www.gnu.org/software/automake/manual/html_node/Tricks-For-Silencing-Make.html&quot;&gt;ways to silence &lt;code&gt;make&lt;/code&gt;&lt;/a&gt;. For instance, prefixing a &lt;code&gt;Makefile&lt;/code&gt; line with &lt;code&gt;@&lt;/code&gt; prevents &lt;code&gt;make&lt;/code&gt; from printing it before execution. I find it best to leave the defaults for manual builds. For builds triggered by file changes during development I completely disable &lt;code&gt;make&lt;/code&gt; output with the &lt;code&gt;-s&lt;/code&gt; flag:&lt;/p&gt;
&lt;pre class=&quot;language-makefile&quot;&gt;&lt;code class=&quot;language-makefile&quot;&gt;&lt;span class=&quot;token target symbol&quot;&gt;watch&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt;
	&lt;span class=&quot;token variable&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;BIN&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;/chokidar &lt;span class=&quot;token string&quot;&gt;&quot;$(SRC)&quot;&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;assets&quot;&lt;/span&gt; -c &lt;span class=&quot;token string&quot;&gt;&quot;make -s&quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I also like to add some blank lines to distinguish the output of consecutive builds:&lt;/p&gt;
&lt;pre class=&quot;language-makefile&quot;&gt;&lt;code class=&quot;language-makefile&quot;&gt;&lt;span class=&quot;token target symbol&quot;&gt;build&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; build-directory html js css fonts images
	&lt;span class=&quot;token operator&quot;&gt;@&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;BIN&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;/browser-sync reload
	&lt;span class=&quot;token operator&quot;&gt;@&lt;/span&gt;echo -e &lt;span class=&quot;token string&quot;&gt;&quot;\n\n\n\n&quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
</content>
    
  </entry>
  
    
  
  <entry>
    <title>API Response Serialization with Plain Old Ruby Objects and ActiveSupport</title>
    <link href="https://dimiterpetrov.com/blog/api-response-serialization-with-plain-old-ruby-objects-and-activesupport/"/>
    <updated>2016-07-30T00:00:00Z</updated>
    <id>https://dimiterpetrov.com/blog/api-response-serialization-with-plain-old-ruby-objects-and-activesupport/</id>
    
    <content type="html">&lt;p&gt;If you are building an API application with Ruby on Rails, you will notice that
the &lt;code&gt;jbuilder&lt;/code&gt; gem is suggested in the default &lt;code&gt;Gemfile&lt;/code&gt;. Jbuilder allows you to
create JSON using a builder domain specific language. Some people prefer
&lt;code&gt;ActiveModel::Serializers&lt;/code&gt; which has its own DSL, is more declarative and
handles structural conventions for you. This is convenient if you want your API
to conform to the &lt;a href=&quot;http://jsonapi.org/&quot;&gt;JSON API specification&lt;/a&gt; or a custom format.&lt;/p&gt;
&lt;p&gt;While these gems (and others) each have their trade-offs, in some projects, they
are more of a hindrance than help. I have another serialization strategy for
these cases.&lt;/p&gt;
&lt;p&gt;The Ruby standard library supports encoding primitives to JSON and ActiveSupport
adds a layer on top of that to allow serialization of any Ruby object by first
serializing it to a Hash. In my code, I build a hash directly:&lt;/p&gt;
&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;PostSerializer&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token function&quot;&gt;initialize&lt;/span&gt;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;post&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;token variable&quot;&gt;@post&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; post
  &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token function&quot;&gt;serialize&lt;/span&gt;&lt;/span&gt;
    &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;token symbol&quot;&gt;title&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; post&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;title&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;
      &lt;span class=&quot;token symbol&quot;&gt;author&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; post&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;author
    &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;token keyword&quot;&gt;private&lt;/span&gt;

  attr_reader &lt;span class=&quot;token symbol&quot;&gt;:post&lt;/span&gt;
&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then in a controller, I would have something like:&lt;/p&gt;
&lt;pre class=&quot;language-ruby&quot;&gt;&lt;code class=&quot;language-ruby&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;token method-definition&quot;&gt;&lt;span class=&quot;token function&quot;&gt;show&lt;/span&gt;&lt;/span&gt;
  post &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Post&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;find&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;params&lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;token symbol&quot;&gt;:id&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;
  render json&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;PostSerializer&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;post&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;serialize&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;to_json
&lt;span class=&quot;token keyword&quot;&gt;end&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
</content>
    
  </entry>
</feed>
