Just A Startup Guy

Ruby on Rails / Javascript Freelancer in San Francisco Bay Area

Wake Up and Smell the (Slow) Tests

I’ve been on many projects where writing/running tests were painfully slow. To test one line of change often requires me to construct a bunch of unrelated models, fix a few failures in other tests and then patiently wait an hour or two for the tests suite to pass locally and on CI.

Even worse, the developers are often proud of themselves for writing the tests first to have an excellent test coverage. These painfully slow processes for writing/running tests are often accepted as the cost of doing TDD.

It is not. These painfully slow tests are a clear hint that the code/test can be structured better. Rather than blindly writing more tests, I usually ask myself the following questions.

Do my tests have too many inter-process communications?

Inter-process communications are an order of magnitutue slower than in-memory communications. Mock out the interfaces if that’s the case. Better yet, try to design a system where these external connections are explicitly inversely injected. Then during the test all is needed is just to swap in a fake connection.

Am I testing at the right level?

Integration tests are a necessary evil and need to be treated as such. Writing an integration test for an ‘if’ statement change in the model can often be inappropriate. Unfortunately, many developers thinks the more the better when writing integration tests, which often leads to a bloated test suite.

Is my test setup complicated?

If I have to create lots of objects to be able to test my object then that’s usually a sign the objects are too coupled together. I’ve seen this happen to a lot in Rails projects since the framework itself does very little to prevent this. If I spend more time setting up the tests than actually writing it, I would investigate it a bit to see if I can decouple the code dependencies.

Can I modulize part of the project?

Modulized code base means a decoupled structure, which leads to a simplier test setup, smaller test suites, and faster test runs.

Are my tests too ‘meta’

Writing tests that generates more tests seems clever but there actually lots of problems:

  • Very easy to go overboard with it: In one of my projects, we went ‘meta’ for generating access control tests - a test for every role (guest, user, admin, etc), and every path combination to make sure current users can/cannot access certain pages. That alone, generated thousands of tests and took a very long time to run.

  • Lots of valueless tests: With tests like these, we often try to test every possible scenario. but when we really only care about the common cases and edge cases.

  • Too abstract: The intent of each test should be very clear and easy to read but that’s usually not the case when tests are ‘meta’.

  • Hard to debug: It’s often hard to run a single generated test without running the entire thing and the line numbers are usually wrong.

Does my test code look boring or repetitive?

Tests should be interesting to write. If they look repetitive, that usually signals repeated usage of code or code that have a common pattern with slight variations. If this occurs, I would review and try to see if I can extract the common pattern out into a behavior and create a separate test just for that behavior.

Using AngularJS for Existing Rails App

In one of my projects, we were tasked to put AngularJS on top of an existing Rails app. We wanted to keep the business impact at a minimum. This means that we had to gradually make the transition while adding more end-user features.

Since retro fitting a client side framework is pretty common for many Rails apps, I’m going to share some of the problems I’ve encoutered, and possible approaches I used.

HAML or HTML?

Our views were written in haml and generated from the server side. I liked it and would like to continue using it for generating view markup. Unfortunatelly, we didn’t find a javascript library that work exact the same as ruby’s haml.

We ended up created a TemplatesController and route all of the template fetching requests though this TemplatesController. From browser’s perspective, we are still serving static HTML, but we were able to write it in HAML from the server side.

1
2
3
4
5
6
7
# TemplatesController
class TemplatesController
  def show
    path = params[:path]
    render file: "/views/templates/#{path}", handler: [:haml], layout: false
  end
end
1
2
3
# router.rb
# routes template/* to a template controller
match '/template/*path' => 'templates#show'

One added benefit was that because we are still on the server side, we have access to things like User::ROLES and I18n.t 'store.title', which we otherwise have to duplicate at the client side.

Do we have to write validations twice?

I really like the ActiveRecord error messages. It has good defaults, easily configurable, and intergate well with i18n. At the same time, I want the good user experience that’s provided by the client-side validation. Refreshing the page to see errors is so 2008.

I also don’t want to write our validations and messages twice. The server side already has validations, we just need to pipe the error messages out to the page via ajax. Its easy with AngualrJS

1
2
3
4
5
6
7
8
<form action='/users', methpd='post'>
  <fieldset>
    <label>Name</label>
    <input type='text' ng-model='name' />
    <div>{{errors['name']}}</div>
  </fieldset>
  <input type='submit' ng-click='submit' />
</form>
1
2
3
4
5
6
7
8
9
function FooCtrl ($scope) {
  $scope.submit = function () {
    httpRequest({}, scope.success, scope.setErrors)
  };

  $scoppe.setErrors = function (response) {
    scope.errors = response.data.errors
  }
}

Every time the user clicks submit, without any client-side validation, the form issues an ajax request. We rely on ActiveRecord to do the validation, feed us with the correct error messages, and store the errors in an errors object in javascript, with Angular’s two-way binding. The view then gets updated automatically.

Note: The approach works for us becuase our application only validates the input after the user attempts to submit the form. It might not work well in your case.

Turn functions into services

We use CanCan for access control. In the view, we show and hide certain elements depending on the current user’s role. The code snippet below hides the Edit button if current_user does not have the ability to manage Post.

1
2
  - if current_user.ability.can?(:manage, Post)
    <button>Edit</button>

Since we are switching to client side templates, we don’t have direct access to ability object anymore, but we can turn the CanCan ability into a service call. A get request to /abilities.json will return something like this for the current user:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
{
  "manage": {
    "User": true,
    "Post": false,
    ...
  },
  "read": {
    "User": true,
    "Post": true
    ...
  },
  "Update": {
    "User": true,
    "Post": false
  }
  ...
}

In the view, we can choose to show/hide elements based on the user’s role again.

1
  <button ng-show="canManage('User')">
1
2
3
4
5
  function FooCtrl () {
    scope.canManage = function (model) {
      scope.manage[model]
    };
  }