Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Branch: master
Fetching contributors…

Cannot retrieve contributors at this time

461 lines (365 sloc) 47.366 kB
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title><![CDATA[Re-factor]]></title>
<link href="http://re-factor.com/atom.xml" rel="self"/>
<link href="http://re-factor.com/"/>
<updated>2014-03-20T10:26:23-04:00</updated>
<id>http://re-factor.com/</id>
<author>
<name><![CDATA[Oren Dobzinski]]></name>
<email><![CDATA[oren@re-factor.com]]></email>
</author>
<generator uri="http://octopress.org/">Octopress</generator>
<entry>
<title type="html"><![CDATA[Guest Post On The Code Climate Blog]]></title>
<link href="http://re-factor.com/blog/2014/03/20/guest-post-on-the-code-climate-blog/"/>
<updated>2014-03-20T09:59:00-04:00</updated>
<id>http://re-factor.com/blog/2014/03/20/guest-post-on-the-code-climate-blog</id>
<content type="html"><![CDATA[<p>Head out to the Code Climate blog and read my guest post there called <a href="http://blog.codeclimate.com/blog/2014/03/20/kickstart-your-next-project-with-a-walking-skeleton/">Kickstart your next project with a Walking Skeleton</a>, where I explain the concept of a Walking Skeleton and how it can keep projects on track by surfacing many of the risks involved with the design and deployment of complex applications. Go ahead and read it and explore the many other great posts there.</p>
<p>Many thanks to Frazer Horn and Susan Potter for reviewing an early draft of the post.</p>
]]></content>
</entry>
<entry>
<title type="html"><![CDATA[Slow Tests are the Symptom, not the Cause]]></title>
<link href="http://re-factor.com/blog/2013/09/27/slow-tests-are-the-symptom-not-the-cause/"/>
<updated>2013-09-27T10:02:00-04:00</updated>
<id>http://re-factor.com/blog/2013/09/27/slow-tests-are-the-symptom-not-the-cause</id>
<content type="html"><![CDATA[<p><strong>Update:</strong> now using <a href="#keyword-arguments">ruby 2.1&rsquo;s keyword arguments syntax</a>. Also added a <a href="#one-level">refactoring step</a>.</p>
<p>If you have a slow test suite and you are asking yourself &ldquo;how can I make my tests faster?&rdquo; then you are asking the wrong question. Most chances are that you have bigger problems than just slow tests. The test slowness is merely the symptom; what you should really address is the cause. Once the real cause is addressed you will find that it&rsquo;s easy to write new fast tests and straightforward to refactor existing tests.</p>
<!-- more -->
<p>It&rsquo;s surprising how quickly a rails app&rsquo;s test suite can become slow. It&rsquo;s important to understand the reason for this slowness early on and address the real cause behind it. In most cases the reason is excessive <em>coupling</em> between the domain objects themselves and coupling between these objects and the framework.</p>
<p>In this refactoring walk-through we will see how small, incremental improvements to the design of a rails app, and specifically, <em>decoupling</em>, naturally lead to faster tests. We will extract service objects, completely remove all rails dependencies in test time and otherwise reduce the amount of coupling in the app.</p>
<p>Our goal is to have a simple, flexible and easy to maintain system in which objects can be replaced with other objects with minimal code changes. We will strive to achieve this goal and observe the effect of it on our tests speed.</p>
<h2>Starting With A Fat Controller</h2>
<p>Suppose we have a controller that&rsquo;s responsible for handling users signing up for a mailing list:</p>
<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
</pre></td><td class='code'><pre><code class='ruby'><span class='line'><span class="k">class</span> <span class="nc">MailingListsController</span> <span class="o">&lt;</span> <span class="no">ApplicationController</span>
</span><span class='line'> <span class="n">respond_to</span> <span class="ss">:json</span>
</span><span class='line'> <span class="k">def</span> <span class="nf">add_user</span>
</span><span class='line'> <span class="n">user</span> <span class="o">=</span> <span class="no">User</span><span class="o">.</span><span class="n">find_by!</span><span class="p">(</span><span class="ss">username</span><span class="p">:</span> <span class="n">params</span><span class="o">[</span><span class="ss">:username</span><span class="o">]</span><span class="p">)</span>
</span><span class='line'> <span class="no">NotifiesUser</span><span class="o">.</span><span class="n">run</span><span class="p">(</span><span class="n">user</span><span class="p">,</span> <span class="s1">&#39;blog_list&#39;</span><span class="p">)</span>
</span><span class='line'> <span class="n">user</span><span class="o">.</span><span class="n">update_attributes</span><span class="p">(</span><span class="n">mailing_list_name</span><span class="p">:</span> <span class="s1">&#39;blog_list&#39;</span><span class="p">)</span>
</span><span class='line'> <span class="n">respond_with</span> <span class="n">user</span>
</span><span class='line'> <span class="k">end</span>
</span><span class='line'><span class="k">end</span>
</span></code></pre></td></tr></table></div></figure>
<p>We first find the user (an exception is raised if the user is not found). Then we notify the user she was added to the mailing list via <code>NotifiesUser</code> (probably asking her to confirm). We update the user record with the name of the mailing list and then hand the <code>user</code> object to <code>respond_with</code>, which will render the json representation of the user or the proper error response in case saving of the object failed.</p>
<p>The logic here is pretty straight-forward, but it&rsquo;s still too complicated for a controller and should be extracted out. But where to? The word <code>user</code> in every line in this method suggests that we should push it into the <code>User</code> model (that&rsquo;s called <a href="http://sourcemaking.com/refactoring/feature-envy">Feature Envy</a>). Let&rsquo;s try this:</p>
<h2>Extracting Logic to a Fat Model</h2>
<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
</pre></td><td class='code'><pre><code class='ruby'><span class='line'><span class="k">class</span> <span class="nc">MailingListsController</span> <span class="o">&lt;</span> <span class="no">ApplicationController</span>
</span><span class='line'> <span class="n">respond_to</span> <span class="ss">:json</span>
</span><span class='line'> <span class="k">def</span> <span class="nf">add_user</span>
</span><span class='line'> <span class="n">user</span> <span class="o">=</span> <span class="no">User</span><span class="o">.</span><span class="n">add_to_mailing_list</span><span class="p">(</span><span class="n">params</span><span class="o">[</span><span class="ss">:username</span><span class="o">]</span><span class="p">,</span> <span class="s1">&#39;blog_list&#39;</span><span class="p">)</span>
</span><span class='line'> <span class="n">respond_with</span> <span class="n">user</span>
</span><span class='line'> <span class="k">end</span>
</span><span class='line'><span class="k">end</span>
</span></code></pre></td></tr></table></div></figure>
<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
</pre></td><td class='code'><pre><code class='ruby'><span class='line'><span class="k">class</span> <span class="nc">User</span> <span class="o">&lt;</span> <span class="ss">ActiveRecord</span><span class="p">:</span><span class="ss">:Base</span>
</span><span class='line'> <span class="n">validates_uniqueness_of</span> <span class="ss">:username</span>
</span><span class='line'>
</span><span class='line'> <span class="k">def</span> <span class="nc">self</span><span class="o">.</span><span class="nf">add_to_mailing_list</span><span class="p">(</span><span class="n">username</span><span class="p">,</span> <span class="n">mailing_list_name</span><span class="p">)</span>
</span><span class='line'> <span class="n">user</span> <span class="o">=</span> <span class="no">User</span><span class="o">.</span><span class="n">find_by!</span><span class="p">(</span><span class="ss">username</span><span class="p">:</span> <span class="n">username</span><span class="p">)</span>
</span><span class='line'> <span class="no">NotifiesUser</span><span class="o">.</span><span class="n">run</span><span class="p">(</span><span class="n">user</span><span class="p">,</span> <span class="s1">&#39;blog_list&#39;</span><span class="p">)</span>
</span><span class='line'> <span class="n">user</span><span class="o">.</span><span class="n">update_attributes</span><span class="p">(</span><span class="n">mailing_list_name</span><span class="p">:</span> <span class="s1">&#39;blog_list&#39;</span><span class="p">)</span>
</span><span class='line'> <span class="n">user</span>
</span><span class='line'> <span class="k">end</span>
</span><span class='line'><span class="k">end</span>
</span></code></pre></td></tr></table></div></figure>
<p>This is better: the <code>User</code> class is now responsible for creating and updating users. But there is a problem: now <code>User</code> is handling mailing list additions, as well as user notifications. These are too many responsibilities for one class. Having an active record object handle anything more than CRUD, associations and validations is a (further) violation of the <a href="http://en.wikipedia.org/wiki/Single_responsibility_principle">Single Responsibility Principle</a>.</p>
<p>The result is that business logic in active record classes is a pain to unit test. You often need to use factories or to heavily stub out methods of the object under test (don&rsquo;t do that), stub all instances of the class under test (don&rsquo;t do that either) or hit the database in your unit tests (please don&rsquo;t). As a result, testing active record objects can be very slow, sometimes orders of magnitude slower than testing plain ruby objects.</p>
<p>Now, if the code above was the entire <code>User</code> class and my application was small and simple I might have been happy with leaving <code>User#add_to_mailing_list</code> as is. But in a bit bigger rails apps that are not groomed often enough, models, controllers and domain logic tend to get tangled (coupled) together and needlessly complicate things (Rich Hickey, the inventor of clojure, calls it <em>incidental complexity</em>). This is when introducing a <a href="http://blog.codeclimate.com/blog/2012/10/17/7-ways-to-decompose-fat-activerecord-models/">service object</a> is helpful:</p>
<h2>Extracting a Service Object</h2>
<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
</pre></td><td class='code'><pre><code class='ruby'><span class='line'><span class="k">class</span> <span class="nc">MailingListsController</span> <span class="o">&lt;</span> <span class="no">ApplicationController</span>
</span><span class='line'> <span class="n">respond_to</span> <span class="ss">:json</span>
</span><span class='line'> <span class="k">def</span> <span class="nf">add_user</span>
</span><span class='line'> <span class="n">user</span> <span class="o">=</span> <span class="no">AddsUserToList</span><span class="o">.</span><span class="n">run</span><span class="p">(</span><span class="n">params</span><span class="o">[</span><span class="ss">:username</span><span class="o">]</span><span class="p">,</span> <span class="s1">&#39;blog_list&#39;</span><span class="p">)</span>
</span><span class='line'> <span class="n">respond_with</span> <span class="n">user</span>
</span><span class='line'> <span class="k">end</span>
</span><span class='line'><span class="k">end</span>
</span></code></pre></td></tr></table></div></figure>
<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
</pre></td><td class='code'><pre><code class='ruby'><span class='line'><span class="k">class</span> <span class="nc">AddsUserToList</span>
</span><span class='line'> <span class="k">def</span> <span class="nc">self</span><span class="o">.</span><span class="nf">run</span><span class="p">(</span><span class="n">username</span><span class="p">,</span> <span class="n">mailing_list_name</span><span class="p">)</span>
</span><span class='line'> <span class="n">user</span> <span class="o">=</span> <span class="no">User</span><span class="o">.</span><span class="n">find_by!</span><span class="p">(</span><span class="ss">username</span><span class="p">:</span> <span class="n">username</span><span class="p">)</span>
</span><span class='line'> <span class="no">NotifiesUser</span><span class="o">.</span><span class="n">run</span><span class="p">(</span><span class="n">user</span><span class="p">,</span> <span class="s1">&#39;blog_list&#39;</span><span class="p">)</span>
</span><span class='line'> <span class="n">user</span><span class="o">.</span><span class="n">update_attributes</span><span class="p">(</span><span class="n">mailing_list_name</span><span class="p">:</span> <span class="s1">&#39;blog_list&#39;</span><span class="p">)</span>
</span><span class='line'> <span class="n">user</span>
</span><span class='line'> <span class="k">end</span>
</span><span class='line'><span class="k">end</span>
</span></code></pre></td></tr></table></div></figure>
<p>We created a plain ruby object, <code>AddsUserToList</code>, which contains the business logic from before. In the controller we call this object and not <code>User</code> directly.
This is an improvement, but hard-coding the name of the class of your collaborator is a bad idea since it couples the two together and makes it impossible to replace the class with a different implementation. Not surprisingly, the result of this coupling is that testing becomes harder and tests slower. Testing this service object would require us to somehow stub <code>User#find_by!</code> to avoid hitting the database, and probably also stub out <code>NotifiesUser#run</code> in order to avoid sending a real notification out.</p>
<p>Also, referencing the class <code>User</code> directly means that our unit tests will have to load active record and the entire rails stack, but even worse &ndash; the entire app and its dependencies. This load time can be a few seconds for trivial rails apps, but can sometimes be 30 seconds for bigger apps. Unit tests should be <em>fast</em> to run as part of your test suite but also fast to run individually, which means they should not load the rails stack or your application (also see <a href="http://www.youtube.com/watch?v=bNn6M2vqxHE">Corey Haines&rsquo;s talk</a>
on the subject).</p>
<p>The most straight forward way to decouple the object from its collaborators is to <em>inject the dependencies</em> of <code>AddsUserToList</code>:</p>
<h2>Injecting Dependencies</h2>
<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
</pre></td><td class='code'><pre><code class='ruby'><span class='line'><span class="k">class</span> <span class="nc">AddsUserToList</span>
</span><span class='line'> <span class="k">def</span> <span class="nc">self</span><span class="o">.</span><span class="nf">run</span><span class="p">(</span><span class="n">username</span><span class="p">,</span> <span class="n">mailing_list_name</span><span class="p">,</span> <span class="n">finds_user</span> <span class="o">=</span> <span class="no">User</span><span class="p">,</span> <span class="n">notifies_user</span> <span class="o">=</span> <span class="no">NotifiesUser</span><span class="p">)</span>
</span><span class='line'> <span class="n">finds_user</span><span class="o">.</span><span class="n">find_by!</span><span class="p">(</span><span class="ss">username</span><span class="p">:</span> <span class="n">username</span><span class="p">)</span>
</span><span class='line'> <span class="n">notifies_user</span><span class="o">.</span><span class="p">(</span><span class="n">user</span><span class="p">,</span> <span class="n">mailing_list_name</span><span class="p">)</span>
</span><span class='line'> <span class="n">user</span><span class="o">.</span><span class="n">update_attributes</span><span class="p">(</span><span class="n">mailing_list_name</span><span class="p">:</span> <span class="n">mailing_list_name</span><span class="p">)</span>
</span><span class='line'> <span class="n">user</span>
</span><span class='line'> <span class="k">end</span>
</span><span class='line'><span class="k">end</span>
</span></code></pre></td></tr></table></div></figure>
<p>We can now pass as an argument any class that finds a user and any class that notifies a user, which means that passing different implementations will be easy. It also means that testing will be easier. Since we supplied reasonable defaults we don&rsquo;t need to be explicit about these dependencies if we don&rsquo;t change them, and our controller can stay unchanged.</p>
<p>The fact that we are specifying <code>User</code> as the default value of finds_user in the parameter list does <em>not</em> mean that this class and all its dependents (ActiveRecord, our app and other gems) will get loaded. Ruby&rsquo;s <em>Deferred Evaluation</em> of the default values means that if these default values are not needed they will not get loaded, so we can run this unit test without loading rails.</p>
<h2>Simplifying the Interface</h2>
<p>The method <code>AddsUserToList#run</code> receives 4 arguments. Users of this method need to know the <em>order</em> of the list. Also, it is likely that over time you&rsquo;d discover you need to add more arguments. When this happens you will need to update all users of the method. A more flexible solution is to use a hash of arguments. This will make the interface more stable and ensure the number of arguments does not grow when we find that we need to add more arguments. It will also make refactoring a little easier, which is important. I often find that for many classes I end up changing from an argument list to a hash of arguments at some point, so why not <a href="http://www.poodr.com/">use it in the first place</a>? But does it mean that we need to give up the advantages of deferred evaluation of the default values? Not at all.</p>
<p>We will use <code>Hash#fetch</code>, passing a block to it, which will not get evaluated unless the queried key is absent. In our tests, the code in the block to <code>fetch</code> will never get evaluated, and <code>User</code> won&rsquo;t get loaded. Also, when specifying the defaults in the argument list it is not possible to evaluate more than one statement, but we can do it using <code>Hash#fetch</code>.</p>
<p>One more thing: when my classes contain only one public method I don&rsquo;t like calling it <code>run</code>, <code>do</code> or <code>perform</code> since these names don&rsquo;t convey a lot of information. In this case I&rsquo;d rather call it <code>call</code> and use ruby&rsquo;s shorthand notation for invoking this method. This also enables me to pass in a proc instead of the class itself if I need it.</p>
<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
</pre></td><td class='code'><pre><code class='ruby'><span class='line'><span class="k">class</span> <span class="nc">AddsUserToList</span>
</span><span class='line'> <span class="k">def</span> <span class="nc">self</span><span class="o">.</span><span class="nf">run</span><span class="p">(</span><span class="n">args</span><span class="p">)</span>
</span><span class='line'> <span class="n">finds_user</span> <span class="o">=</span> <span class="n">args</span><span class="o">.</span><span class="n">fetch</span><span class="p">(</span><span class="ss">:finds_user</span><span class="p">)</span> <span class="p">{</span> <span class="no">User</span> <span class="p">}</span>
</span><span class='line'> <span class="n">notifies_user</span> <span class="o">=</span> <span class="n">args</span><span class="o">.</span><span class="n">fetch</span><span class="p">(</span><span class="ss">:notifies_user</span><span class="p">)</span> <span class="p">{</span> <span class="no">NotifiesUser</span> <span class="p">}</span>
</span><span class='line'>
</span><span class='line'> <span class="n">finds_user</span><span class="o">.</span><span class="n">find_by!</span><span class="p">(</span><span class="ss">username</span><span class="p">:</span> <span class="n">args</span><span class="o">.</span><span class="n">fetch</span><span class="p">(</span><span class="ss">:username</span><span class="p">))</span>
</span><span class='line'> <span class="n">notifies_user</span><span class="o">.</span><span class="p">(</span><span class="n">user</span><span class="p">,</span> <span class="n">args</span><span class="o">.</span><span class="n">fetch</span><span class="p">(</span><span class="ss">:mailing_list_name</span><span class="p">))</span>
</span><span class='line'> <span class="n">user</span><span class="o">.</span><span class="n">update_attributes</span><span class="p">(</span><span class="n">mailing_list_name</span><span class="p">:</span> <span class="n">args</span><span class="o">.</span><span class="n">fetch</span><span class="p">(</span><span class="ss">:mailing_list_name</span><span class="p">))</span>
</span><span class='line'> <span class="n">user</span>
</span><span class='line'> <span class="k">end</span>
</span><span class='line'><span class="k">end</span>
</span></code></pre></td></tr></table></div></figure>
<h2><a id="keyword-arguments">Using Ruby 2.1&rsquo;s Keyword Arguments Syntax</h2>
<p>We can get the same exact functionality by using ruby&rsquo;s 2.1&rsquo;s keyword argument syntax. See how much less verbose this version is:</p>
<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
</pre></td><td class='code'><pre><code class='ruby'><span class='line'><span class="k">class</span> <span class="nc">AddsUserToList</span>
</span><span class='line'> <span class="k">def</span> <span class="nc">self</span><span class="o">.</span><span class="nf">call</span><span class="p">(</span><span class="ss">username</span><span class="p">:,</span> <span class="n">mailing_list_name</span><span class="p">:,</span> <span class="n">finds_user</span><span class="p">:</span> <span class="no">User</span><span class="p">,</span>
</span><span class='line'> <span class="n">notifies_user</span><span class="p">:</span> <span class="no">NotifiesUser</span><span class="p">)</span>
</span><span class='line'> <span class="n">user</span> <span class="o">=</span> <span class="n">finds_user</span><span class="o">.</span><span class="n">find_by_username!</span><span class="p">(</span><span class="n">username</span><span class="p">)</span>
</span><span class='line'> <span class="n">notifies_user</span><span class="o">.</span><span class="p">(</span><span class="n">user</span><span class="p">,</span> <span class="n">mailing_list_name</span><span class="p">)</span>
</span><span class='line'> <span class="n">user</span><span class="o">.</span><span class="n">add_to_mailing_list</span><span class="p">(</span><span class="n">mailing_list_name</span><span class="p">)</span>
</span><span class='line'> <span class="n">user</span>
</span><span class='line'> <span class="k">end</span>
</span><span class='line'><span class="k">end</span>
</span></code></pre></td></tr></table></div></figure>
<p>Note that <code>username</code> and <code>mailing_list_name</code> are required named arguments, and will raise an <code>ArgumentError</code> if not passed in (this is not available even in ruby version 2.0), whereas the other arguments will get the specified default value (evaluated and) assigned to them if not passed in.</p>
<h2><a id="one-level"></a>Using One Level of Abstraction Per Method</h2>
<p>There is still something that doesn&rsquo;t feel quite right. Consider <a href="http://www.amazon.com/Smalltalk-Best-Practice-Patterns-Kent/dp/013476904X">Kent Beck&rsquo;s advice</a>:</p>
<blockquote><p>Divide your program into methods that perform one identifiable task. Keep all of the operations in a method at the same level of abstraction.</p></blockquote>
<p>The <code>call</code> method invokes a few other methods, but these methods operate on different levels of abstractions: notifying a user is a domain-level concept, whereas updating a user&rsquo;s attributes is a lower-level, persistence related concept. Another way to put it: while the details of how exactly the user is going to be notified are hidden, the details of updating the attributes are exposed. The fact that active record gives us multiple ways of updating attributes makes this problem even clearer: what if we wanted to update attributes using accessors and save using active record&rsquo;s <code>save</code>? These details are irrelevant at the abstraction level of the method we&rsquo;re in and the method should not change if they do. <code>notifies_user</code> is treated as a role, while user is wrongly treated as the active record&rsquo;s implementation of a user role.</p>
<p>The call to <code>find_by!</code> also operates on a lower level than <code>notifis_user</code>&rsquo;s, similarly to the case of <code>update_attributes</code>. In order to fix these problems we create an instance method and a class method in <code>User</code>:</p>
<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
</pre></td><td class='code'><pre><code class='ruby'><span class='line'><span class="k">class</span> <span class="nc">User</span> <span class="o">&lt;</span> <span class="ss">ActiveRecord</span><span class="p">:</span><span class="ss">:Base</span>
</span><span class='line'> <span class="n">validates_uniqueness_of</span> <span class="ss">:username</span>
</span><span class='line'>
</span><span class='line'> <span class="k">def</span> <span class="nf">add_to_mailing_list</span><span class="p">(</span><span class="n">list_name</span><span class="p">)</span>
</span><span class='line'> <span class="n">update_attributes</span><span class="p">(</span><span class="n">mailing_list_name</span><span class="p">:</span> <span class="n">list_name</span><span class="p">)</span>
</span><span class='line'> <span class="k">end</span>
</span><span class='line'>
</span><span class='line'> <span class="k">def</span> <span class="nc">self</span><span class="o">.</span><span class="nf">find_by_username!</span><span class="p">(</span><span class="n">username</span><span class="p">)</span>
</span><span class='line'> <span class="n">find_by!</span><span class="p">(</span><span class="ss">username</span><span class="p">:</span> <span class="n">username</span><span class="p">)</span>
</span><span class='line'> <span class="k">end</span>
</span><span class='line'><span class="k">end</span>
</span></code></pre></td></tr></table></div></figure>
<p>The specific query to find the user or the specific active record used are now <code>User</code>&rsquo;s business. We achieved further decoupling from active record and made sure the abstraction level is right. If we keep using our own methods instead of active record&rsquo;s we&rsquo;ve effectively made persistence and object lookup an implementation detail that&rsquo;s the concern of the model only.</p>
<p>The end result looks like this:</p>
<h2>The Complete Refactoring</h2>
<p>Before:</p>
<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
</pre></td><td class='code'><pre><code class='ruby'><span class='line'><span class="k">class</span> <span class="nc">MailingListsController</span> <span class="o">&lt;</span> <span class="no">ApplicationController</span>
</span><span class='line'> <span class="n">respond_to</span> <span class="ss">:json</span>
</span><span class='line'> <span class="k">def</span> <span class="nf">add_user</span>
</span><span class='line'> <span class="n">user</span> <span class="o">=</span> <span class="no">User</span><span class="o">.</span><span class="n">find_by!</span><span class="p">(</span><span class="ss">username</span><span class="p">:</span> <span class="n">params</span><span class="o">[</span><span class="ss">:username</span><span class="o">]</span><span class="p">)</span>
</span><span class='line'> <span class="no">NotifiesUser</span><span class="o">.</span><span class="n">run</span><span class="p">(</span><span class="n">user</span><span class="p">,</span> <span class="s1">&#39;blog_list&#39;</span><span class="p">)</span>
</span><span class='line'> <span class="n">user</span><span class="o">.</span><span class="n">update_attributes</span><span class="p">(</span><span class="n">mailing_list_name</span><span class="p">:</span> <span class="s1">&#39;blog_list&#39;</span><span class="p">)</span>
</span><span class='line'> <span class="n">respond_with</span> <span class="n">user</span>
</span><span class='line'> <span class="k">end</span>
</span><span class='line'><span class="k">end</span>
</span></code></pre></td></tr></table></div></figure>
<p>After:</p>
<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
</pre></td><td class='code'><pre><code class='ruby'><span class='line'><span class="k">class</span> <span class="nc">MailingListsController</span> <span class="o">&lt;</span> <span class="no">ApplicationController</span>
</span><span class='line'> <span class="n">respond_to</span> <span class="ss">:json</span>
</span><span class='line'> <span class="k">def</span> <span class="nf">add_user</span>
</span><span class='line'> <span class="n">user</span> <span class="o">=</span> <span class="no">AddsUserToList</span><span class="o">.</span><span class="p">(</span><span class="ss">username</span><span class="p">:</span> <span class="n">params</span><span class="o">[</span><span class="ss">:username</span><span class="o">]</span><span class="p">,</span> <span class="n">mailing_list_name</span><span class="p">:</span> <span class="s1">&#39;blog_list&#39;</span><span class="p">)</span>
</span><span class='line'> <span class="n">respond_with</span> <span class="n">user</span>
</span><span class='line'> <span class="k">end</span>
</span><span class='line'><span class="k">end</span>
</span></code></pre></td></tr></table></div></figure>
<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
</pre></td><td class='code'><pre><code class='ruby'><span class='line'><span class="k">class</span> <span class="nc">AddsUserToList</span>
</span><span class='line'> <span class="k">def</span> <span class="nc">self</span><span class="o">.</span><span class="nf">call</span><span class="p">(</span><span class="ss">username</span><span class="p">:,</span> <span class="n">mailing_list_name</span><span class="p">:,</span> <span class="n">finds_user</span><span class="p">:</span> <span class="no">User</span><span class="p">,</span>
</span><span class='line'> <span class="n">notifies_user</span><span class="p">:</span> <span class="no">NotifiesUser</span><span class="p">)</span>
</span><span class='line'> <span class="n">user</span> <span class="o">=</span> <span class="n">finds_user</span><span class="o">.</span><span class="n">find_by_username!</span><span class="p">(</span><span class="n">username</span><span class="p">)</span>
</span><span class='line'> <span class="n">notifies_user</span><span class="o">.</span><span class="p">(</span><span class="n">user</span><span class="p">,</span> <span class="n">mailing_list_name</span><span class="p">)</span>
</span><span class='line'> <span class="n">user</span><span class="o">.</span><span class="n">add_to_mailing_list</span><span class="p">(</span><span class="n">mailing_list_name</span><span class="p">)</span>
</span><span class='line'> <span class="n">user</span>
</span><span class='line'> <span class="k">end</span>
</span><span class='line'><span class="k">end</span>
</span></code></pre></td></tr></table></div></figure>
<h2>The Tests</h2>
<p>The class <code>AddsUserToList</code> can be tested using <em>true</em>, isolated unit tests: we can easily isolate the class under test and make sure it properly communicates with its collaborators. There is no database access, no heavy handed request stubbing and if we want to &ndash; no loading of the rails stack. In fact, I&rsquo;d argue that any test that requires any of the above is not a unit test, but rather an integration test (see entire repo <a href="https://github.com/orend/register">here</a>).</p>
<figure class='code'><figcaption><span></span></figcaption><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
</pre></td><td class='code'><pre><code class='ruby'><span class='line'><span class="n">describe</span> <span class="no">AddsUserToList</span> <span class="k">do</span>
</span><span class='line'> <span class="n">let</span><span class="p">(</span><span class="ss">:finds_user</span><span class="p">)</span> <span class="p">{</span> <span class="n">double</span><span class="p">(</span><span class="s1">&#39;finds_user&#39;</span><span class="p">)</span> <span class="p">}</span>
</span><span class='line'> <span class="n">let</span><span class="p">(</span><span class="ss">:notifies_user</span><span class="p">)</span> <span class="p">{</span> <span class="n">double</span><span class="p">(</span><span class="s1">&#39;notifies_user&#39;</span><span class="p">)</span> <span class="p">}</span>
</span><span class='line'> <span class="n">let</span><span class="p">(</span><span class="ss">:user</span><span class="p">)</span> <span class="p">{</span> <span class="n">double</span><span class="p">(</span><span class="s1">&#39;user&#39;</span><span class="p">)</span> <span class="p">}</span>
</span><span class='line'> <span class="n">subject</span><span class="p">(</span><span class="ss">:adds_user_to_list</span><span class="p">)</span> <span class="p">{</span> <span class="no">AddsUserToList</span> <span class="p">}</span>
</span><span class='line'>
</span><span class='line'> <span class="n">it</span> <span class="s1">&#39;registers a new user&#39;</span> <span class="k">do</span>
</span><span class='line'> <span class="n">expect</span><span class="p">(</span><span class="n">finds_user</span><span class="p">)</span><span class="o">.</span><span class="n">to</span> <span class="n">receive</span><span class="p">(</span><span class="ss">:find_by_username!</span><span class="p">)</span><span class="o">.</span><span class="n">with</span><span class="p">(</span><span class="s1">&#39;username&#39;</span><span class="p">)</span><span class="o">.</span><span class="n">and_return</span><span class="p">(</span><span class="n">user</span><span class="p">)</span>
</span><span class='line'> <span class="n">expect</span><span class="p">(</span><span class="n">notifies_user</span><span class="p">)</span><span class="o">.</span><span class="n">to</span> <span class="n">receive</span><span class="p">(</span><span class="ss">:call</span><span class="p">)</span><span class="o">.</span><span class="n">with</span><span class="p">(</span><span class="n">user</span><span class="p">,</span> <span class="s1">&#39;list_name&#39;</span><span class="p">)</span>
</span><span class='line'> <span class="n">expect</span><span class="p">(</span><span class="n">user</span><span class="p">)</span><span class="o">.</span><span class="n">to</span> <span class="n">receive</span><span class="p">(</span><span class="ss">:add_to_mailing_list</span><span class="p">)</span><span class="o">.</span><span class="n">with</span><span class="p">(</span><span class="s1">&#39;list_name&#39;</span><span class="p">)</span>
</span><span class='line'>
</span><span class='line'> <span class="n">adds_user_to_list</span><span class="o">.</span><span class="p">(</span><span class="ss">username</span><span class="p">:</span> <span class="s1">&#39;username&#39;</span><span class="p">,</span> <span class="n">mailing_list_name</span><span class="p">:</span> <span class="s1">&#39;list_name&#39;</span><span class="p">,</span> <span class="n">finds_user</span><span class="p">:</span> <span class="n">finds_user</span><span class="p">,</span> <span class="n">notifies_user</span><span class="p">:</span> <span class="n">notifies_user</span><span class="p">)</span>
</span><span class='line'> <span class="k">end</span>
</span><span class='line'><span class="k">end</span>
</span></code></pre></td></tr></table></div></figure>
<p>Here we pass in mocks (initialized with <code>#double</code>) for each collaborator and expect them to receive the correct messages. We do not assert any values &ndash; specifically not the value of <code>user.mailing_list_name</code>. Instead we require that <code>user</code> receives the <code>add_to_mailing_list</code> message. We need to <em>trust</em> <code>user</code> to update the attributes. After all, that&rsquo;s a unit test for <code>AddsUserToList</code>, not for <code>user</code>.</p>
<p>Note that the fact that we pushed <code>update_attributes</code> to <code>User</code> helps us avoid a mocking pitfall: you need to <a href="http://www.amazon.com/Growing-Object-Oriented-Software-Guided-Tests/dp/0321503627">only mock types you own</a>. Technically we do own <code>User</code>, but most of its interface is coming down the inheritance tree from <code>ActiveRecord::Base</code>, which we don&rsquo;t own. There is no design feedback when you mock parts of the interface you don&rsquo;t own. Or rather &ndash; you do get feedback but you can&rsquo;t act on it.</p>
<p>As you can see there is a close resemblance between the test code and the code it is testing. I don&rsquo;t see it as a problem. A unit test should verify that the object under test sends the correct messages to its collaborators, and in the case of <code>AddsUserToList</code> we have a controller-like object, and a controller&rsquo;s job is to&hellip; coordinate sending messages between collaborators. Sandi Metz talks about what you should and what you should not test <a href="http://www.confreaks.com/videos/2452-railsconf2013-the-magic-tricks-of-testing">here</a>. To use her vocabulary, all we are testing here are outgoing command messages since these are the only messages this object sends. For that reason I think the resemblance is acceptable.</p>
<p>I should mention that TDD classicists have long criticized TDD mockists (as myself) for writing tests that are too coupled to the implementation. You can read more about it in Martin Fowler&rsquo;s <a href="http://martinfowler.com/articles/mocksArentStubs.html">Mocks Aren&rsquo;t Stubs</a>.</p>
<p>I omit the controller and integration tests here, but <a href="http://solnic.eu/2012/02/02/yes-you-should-write-controller-tests.html">please don&rsquo;t forget them</a> in your code. They will be much simpler and there will be fewer of them if you extract service objects.</p>
<h2>Some Numbers</h2>
<p>How much faster is this test from a unit test that touches the database and loads rails and the application? Here are the results:</p>
<table>
<thead>
<tr>
<th></th>
<th> </th>
<th align="center"> Single Test Runtime </th>
<th align="center"> Total Suite Runtime </th>
</tr>
</thead>
<tbody>
<tr>
<td></td>
<td> <strong>&lsquo;false&rsquo; unit test</strong> </td>
<td align="center"> 0.0530s </td>
<td align="center"> 2.5s</td>
</tr>
<tr>
<td></td>
<td> <strong>true unit test</strong> </td>
<td align="center"> 0.0005s </td>
<td align="center"> 0.4s</td>
</tr>
</tbody>
</table>
<br>
<p>A single test run is roughly <strong>a hundred times faster</strong>. The absolute times are rather small but the difference will be very noticeable when you have hundreds of unit tests or more. The total runtime in the &ldquo;false&rdquo; version takes roughly two seconds longer. This is the time it takes to load a trivial rails app on my machine. This will be significantly higher when the app grows in size and adds more gems.</p>
<h2>Conclusion</h2>
<p>The &lsquo;before&rsquo; version&rsquo;s tests are harder to write and are significantly slower since we bundle many responsibilities into a single class, the controller class. The &lsquo;After&rsquo; version is easier to test (we pass mocks to override the default classes). This means that in our code in <code>AddsUserToList</code> we can easily replace the collaborators with other implementations in case the requirements change and require no or little code change. The controller has been reduced to performing the most basic task of coordination between a few objects.</p>
<p>Is the &lsquo;After&rsquo; version better? I think it is. It&rsquo;s easier and faster to test, but more importantly the collaborators are clearly defined and are treated as <em>roles</em>, not as specific implementations. As such, they can always be replaced by different implementations of the role they play. We now can concentrate on the <em>messages</em> passing between the different <em>roles</em> in our system.</p>
<p>When you practice TDD with mock objects you will almost be forced to inject your dependencies in order to mock collaborators. Extracting your business logic into service objects makes all this much easier, and further decoupling from active record makes the tests true unit tests that are also blazing fast.</p>
<p>This brings us closer to a lofty design goal stated by Kent Beck:</p>
<blockquote><p>When you can extend a system solely by adding new objects without modifying any existing objects, then you have a system that is flexible and cheap to maintain.</p></blockquote>
<p>Using mocks and dependency injection with TDD makes sure your system is designed for this form of modularity from the get go. You know you can replace your objects with a different implementation because this is exactly what you did in your tests when you passed in mocks. Such design guarantees that you can write true, isolated and thus fast, tests.</p>
<p>P.S., if you found this post useful be sure to sign up for the <a href="http://eepurl.com/FF4ET">newsletter</a> to get tips about how to improve your code.</p>
<hr>
<p>I&rsquo;d like to thank the following people for providing useful feedback about this post: James Edward Gray II, Frazer Horn, Steve Klabnik, Peter Marreck, Susan Potter and Piotr Solnica.</p>
]]></content>
</entry>
</feed>
Jump to Line
Something went wrong with that request. Please try again.