Click here to close now.



Welcome!

Ruby-On-Rails Authors: Pat Romanski, Elizabeth White, Hovhannes Avoyan, Yeshim Deniz, PagerDuty Blog

Related Topics: Java IoT, Microsoft Cloud, Open Source Cloud, IoT User Interface, Ruby-On-Rails

Java IoT: Blog Post

Slow Tests Are the Symptom, Not the Cause

Test slowness is merely the symptom; what you should really address is the cause

If you have a slow test suite and you are asking yourself "how can I make my tests faster?" 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's easy to write new fast tests and straightforward to refactor existing tests.

It's surprising how quickly a rails app's test suite can become slow. It'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 coupling between the domain objects themselves and coupling between these objects and the framework.

In this refactoring walk-through we will see how small, incremental improvements to the design of a rails app, and specifically, decoupling, 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.

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.

Starting with a Fat Controller
Suppose we have a controller that's responsible for handling users signing up for a mailing list:

class MailingListsController < ApplicationController

respond_to :json

def add_user

user = User.find_by!(username: params[:username])

NotifiesUser.run(user, 'blog_list')

user.update_attributes(mailing_list_name: 'blog_list')

respond_with user

end

end

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 NotifiesUser (probably asking her to confirm). We update the user record with the name of the mailing list and then hand the user object to respond_with, which will render the json representation of the user or the proper error response in case saving of the object failed.

The logic here is pretty straight-forward, but it's still too complicated for a controller and should be extracted out. But where to? The word user in every line in this method suggests that we should push it into the User model (that's called Feature Envy). Let's try this:

Extracting Logic to a Fat Model

class MailingListsController < ApplicationController

respond_to :json

def add_user

user = User.add_to_mailing_list(params[:username], 'blog_list')

respond_with user

end

end

class User < ActiveRecord::Base

validates_uniqueness_of :username

def self.add_to_mailing_list(username, mailing_list_name)

user = User.find_by!(username: username)

NotifiesUser.run(user, 'blog_list')

user.update_attributes(mailing_list_name: 'blog_list')

user

end

end

This is better: the User class is now responsible for creating and updating users. But there is a problem: now User 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 Single Responsibility Principle.

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't do that), stub all instances of the class under test (don't do that either) or hit the database in your unit tests (please don't). As a result, testing active record objects can be very slow, sometimes orders of magnitude slower than testing plain ruby objects.

Now, if the code above was the entire User class and my application was small and simple I might have been happy with leaving User#add_to_mailing_list 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 incidental complexity). This is when introducing a service objectis helpful:

Extracting a Service Object

class MailingListsController < ApplicationController
respond_to :json
def add_user
user = AddsUserToList.run(params[:username], 'blog_list')
respond_with user
end
end
class AddsUserToList
def self.run(username, mailing_list_name)
user = User.find_by!(username: username)
NotifiesUser.run(user, 'blog_list')
user.update_attributes(mailing_list_name: 'blog_list')
user
end
end

We created a plain ruby object, AddsUserToList, which contains the business logic from before. In the controller we call this object and not User 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 User#find_by! to avoid hitting the database, and probably also stub out NotifiesUser#run in order to avoid sending a real notification out.

Also, referencing the class User directly means that our unit tests will have to load active record and the entire rails stack, but even worse - 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 fast 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 seeCorey Haines's talk on the subject).

The most straight forward way to decouple the object from its collaborators is to inject the dependencies of AddsUserToList:

Injecting Dependencies

class AddsUserToList
def self.run(username, mailing_list_name, finds_user = User, notifies_user = NotifiesUser)
finds_user.find_by!(username: username)
notifies_user.(user, mailing_list_name)
user.update_attributes(mailing_list_name: mailing_list_name)
user
end
end

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't need to be explicit about these dependencies if we don't change them, and our controller can stay unchanged.

The fact that we are specifying User as the default value of finds_user in the parameter list does not mean that this class and all its dependents (ActiveRecord, our app and other gems) will get loaded. Ruby's Deferred Evaluation 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.

Simplifying the Interface
The method AddsUserToList#run receives 4 arguments. Users of this method need to know the order of the list. Also, it is likely that over time you'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 use it in the first place? But does it mean that we need to give up the advantages of deferred evaluation of the default values? Not at all.

We will use Hash#fetch, 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 fetch will never get evaluated, and User won'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 Hash#fetch.

One more thing: when my classes contain only one public method I don't like calling it rundo or perform since these names don't convey a lot of information. In this case I'd rather call it call and use ruby'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.

class AddsUserToList
def self.run(args)
finds_user = args.fetch(:finds_user) { User }
notifies_user = args.fetch(:notifies_user) { NotifiesUser }
finds_user.find_by!(username: args.fetch(:username))
notifies_user.(user, args.fetch(:mailing_list_name))
user.update_attributes(mailing_list_name: args.fetch(:mailing_list_name))
user
end
end

Using Ruby 2.1's Keyword Arguments Syntax

We can get the same exact functionality by using ruby's 2.1's keyword argument syntax. See how much less verbose this version is:

class AddsUserToList
def self.call(username:, mailing_list_name:, finds_user: User,
notifies_user: NotifiesUser)
user = finds_user.find_by_username!(username)
notifies_user.(user, mailing_list_name)
user.add_to_mailing_list(mailing_list_name)
user
end
end

More Stories By Manuel Weiss

I am the cofounder of Codeship – a hosted Continuous Integration and Deployment platform for web applications. On the Codeship blog we love to write about Software Testing, Continuos Integration and Deployment. Also check out our weekly screencast series 'Testing Tuesday'!

@ThingsExpo Stories
"What is the next step in the evolution of IoT systems? The answer is data, information, which is a radical shift from assets, from things to input for decision making," stated Michael Minkevich, VP of Technology Services at Luxoft, in this SYS-CON.tv interview at @ThingsExpo, held November 3-5, 2015, at the Santa Clara Convention Center in Santa Clara, CA.
"IoT is really hitting its stride. The adoption rates are increasing and Vitria is in a good position to help people deliver on the value of IoT," explained Mike Houston, Marketing & Product Marketing Professional at Vitria Technology, in this SYS-CON.tv interview at @ThingsExpo, held November 3-5, 2015, at the Santa Clara Convention Center in Santa Clara, CA.
"IoT is going to be a huge industry with a lot of value for end users, for industries, for consumers, for manufacturers. How can we use cloud to effectively manage IoT applications," stated Ian Khan, Innovation & Marketing Manager at Solgeniakhela, in this SYS-CON.tv interview at @ThingsExpo, held November 3-5, 2015, at the Santa Clara Convention Center in Santa Clara, CA.
Contrary to mainstream media attention, the multiple possibilities of how consumer IoT will transform our everyday lives aren’t the only angle of this headline-gaining trend. There’s a huge opportunity for “industrial IoT” and “Smart Cities” to impact the world in the same capacity – especially during critical situations. For example, a community water dam that needs to release water can leverage embedded critical communications logic to alert the appropriate individuals, on the right device, as soon as they are needed to take action.
When it comes to IoT in the enterprise, namely the commercial building and hospitality markets, a benefit not getting the attention it deserves is energy efficiency, and IoT’s direct impact on a cleaner, greener environment when installed in smart buildings. Until now clean technology was offered piecemeal and led with point solutions that require significant systems integration to orchestrate and deploy. There didn't exist a 'top down' approach that can manage and monitor the way a Smart Building actually breathes - immediately flagging overheating in a closet or over cooling in unoccupied ho...
WebRTC services have already permeated corporate communications in the form of videoconferencing solutions. However, WebRTC has the potential of going beyond and catalyzing a new class of services providing more than calls with capabilities such as mass-scale real-time media broadcasting, enriched and augmented video, person-to-machine and machine-to-machine communications. In his session at @ThingsExpo, Luis Lopez, CEO of Kurento, introduced the technologies required for implementing these ideas and some early experiments performed in the Kurento open source software community in areas such...
"We announced CryptoScript, it's a new way of programming a hardware security module, which technically requires standard APIs and very specific knowledge. With CryptoScript we hope to change that a bit," explained Johannes Lintzen, Vice President of Sales at Utimaco, in this SYS-CON.tv interview at 17th Cloud Expo, held November 3-5, 2015, at the Santa Clara Convention Center in Santa Clara, CA.
There are so many tools and techniques for data analytics that even for a data scientist the choices, possible systems, and even the types of data can be daunting. In his session at @ThingsExpo, Chris Harrold, Global CTO for Big Data Solutions for EMC Corporation, showed how to perform a simple, but meaningful analysis of social sentiment data using freely available tools that take only minutes to download and install. Participants received the download information, scripts, and complete end-to-end walkthrough of the analysis from start to finish, and were also given the practical knowledge ...
"At Sensorberg we are providing a cloud-based beacon management platform and this allows you to control the various beacons that you have in your fleet as well as design various campaigns and triggers which the beacons will initiate," explained Daniel Gillard, Business Development Manager at Sensorberg GmbH, in this SYS-CON.tv interview at @ThingsExpo, held November 3-5, 2015, at the Santa Clara Convention Center in Santa Clara, CA.
Organizations already struggle with the simple collection of data resulting from the proliferation of IoT, lacking the right infrastructure to manage it. They can't only rely on the cloud to collect and utilize this data because many applications still require dedicated infrastructure for security, redundancy, performance, etc. In his session at 17th Cloud Expo, Emil Sayegh, CEO of Codero Hosting, discussed how in order to resolve the inherent issues, companies need to combine dedicated and cloud solutions through hybrid hosting – a sustainable solution for the data required to manage IoT de...
Consumer IoT applications provide data about the user that just doesn’t exist in traditional PC or mobile web applications. This rich data, or “context,” enables the highly personalized consumer experiences that characterize many consumer IoT apps. This same data is also providing brands with unprecedented insight into how their connected products are being used, while, at the same time, powering highly targeted engagement and marketing opportunities. In his session at @ThingsExpo, Nathan Treloar, President and COO of Bebaio, explored examples of brands transforming their businesses by tappi...
The broad selection of hardware, the rapid evolution of operating systems and the time-to-market for mobile apps has been so rapid that new challenges for developers and engineers arise every day. Security, testing, hosting, and other metrics have to be considered through the process. In his session at @ThingsExpo, Walter Maguire, Chief Field Technologist, HP Big Data Group, at Hewlett-Packard, discussed the challenges faced by developers and a composite Big Data applications builder, focusing on how to help solve the problems that developers are continuously battling.
"As a technology provider we believe that business comes first and customers should start thinking that technology is something that helps them to enable new business models," stated Ermanno Bonifazi, Founder and CEO of Solgenia, in this SYS-CON.tv interview at 17th Cloud Expo, held November 3-5, 2015, at the Santa Clara Convention Center in Santa Clara, CA.
"We're seeing a lot of activity in IoT in the healthcare space - a lot of new devices coming in. We are seeing a huge demand in building smart offices, smart infrastructures, smart cloud applications," explained Shrikant Pattathil, President of Harbinger Systems, in this SYS-CON.tv interview at 17th Cloud Expo, held November 3-5, 2015, at the Santa Clara Convention Center in Santa Clara, CA.
"Storage is growing. All of IDC's estimates say that unstructured data is now 80% of the world's data. We provide storage systems that can actually deal with that scale of data - software-defined storage systems," stated Paul Turner, Chief Product and Marketing Officer at Cloudian, in this SYS-CON.tv interview at 17th Cloud Expo, held November 3-5, 2015, at the Santa Clara Convention Center in Santa Clara, CA.
Internet of @ThingsExpo, taking place June 7-9, 2016 at Javits Center, New York City and Nov 1-3, 2016, at the Santa Clara Convention Center in Santa Clara, CA, is co-located with the 18th International @CloudExpo and will feature technical sessions from a rock star conference faculty and the leading industry players in the world and ThingsExpo New York Call for Papers is now open.
With major technology companies and startups seriously embracing IoT strategies, now is the perfect time to attend @ThingsExpo 2016 in New York and Silicon Valley. Learn what is going on, contribute to the discussions, and ensure that your enterprise is as "IoT-Ready" as it can be! Internet of @ThingsExpo, taking place Nov 3-5, 2015, at the Santa Clara Convention Center in Santa Clara, CA, is co-located with 17th Cloud Expo and will feature technical sessions from a rock star conference faculty and the leading industry players in the world. The Internet of Things (IoT) is the most profound cha...
Electric power utilities face relentless pressure on their financial performance, and reducing distribution grid losses is one of the last untapped opportunities to meet their business goals. Combining IoT-enabled sensors and cloud-based data analytics, utilities now are able to find, quantify and reduce losses faster – and with a smaller IT footprint. Solutions exist using Internet-enabled sensors deployed temporarily at strategic locations within the distribution grid to measure actual line loads.
"The problem with IoT today is that people aren't looking to buy IoT, what they're really trying to do is buy a business outcome or trying to figure out ways to improve the business outcome. It just so happens that IoT may be the technology that can help do that," stated Dave McCarthy, Director of Products at Bsquare Corporation, in this SYS-CON.tv interview at @ThingsExpo, held November 3-5, 2015, at the Santa Clara Convention Center in Santa Clara, CA.
There are over 120 breakout sessions in all, with Keynotes, General Sessions, and Power Panels adding to three days of incredibly rich presentations and content. Join @ThingsExpo conference chair Roger Strukhoff (@IoT2040), June 7-9, 2016 in New York City, for three days of intense 'Internet of Things' discussion and focus, including Big Data's indespensable role in IoT, Smart Grids and Industrial Internet of Things, Wearables and Consumer IoT, as well as (new) IoT's use in Vertical Markets.