When you work on applications that your clients are actively using, you want to be 100% sure you don't break something when deploying a new feature.
We all know the feeling when your ticketing system starts screaming at you with bug reports because you've deployed some buggy code and your application has started to misbehave. With that in mind, I'll show you how our Ruby on Rails development team handles this with testing and a Continuous Integration process.
What is continuous integration?
Continuous Integration (CI) is a software development practice where members of a team integrate their work as frequently as possible to reduce integration problems and code conflicts. Every push to a source code repository is verified by an automated build and a test suite to detect errors as quickly as possible. A good CI tool also has a good notification system which effectively notifies all the team members about conflicts and issues with their code.
Building a test suite
First of all, to get started with any sort of Continuous Integration - you need to write tests. Without a test suite, it's impossible to check if changes to a codebase broke something; well, at least in an automated way. You can always manually open every view, every form, and try every action in your application after you change something, but that's hardly an efficient way to do it.
We mostly write unit tests and test model validations, scopes, callbacks, associations and complex methods to be sure the model is behaving as it should. We write functional tests to check controller actions, filters, redirects, view rendering, and the like.
Functional tests also help us to approximately check for errors in our view files. If we have an error inside a view file, the controller test using that view file will fail while rendering it, and we'll easily find the source of the error.
We use several gems to help us in writing the tests:
- FactoryGirl - we use it for quick building and creating instances of objects
- Shoulda - great helpers which speed up writing simple and iterative tests, for example, testing model validations
- Jasmine - a BDD testing framework
- jasmine-rails - a gem which helps us integrate Jasmine into the standard Ruby on Rails testing workflow.
Automation by Semaphore
When we have a good test suite, we want to further automate our workflow. You can go two ways here - set up and maintain your own CI server or go with a hosted one and just pay someone else to do it for you.
We went with a great hosted CI solution called Semaphore. Semaphore connects to your Github repositories and receives an automatic trigger for every code commit. From there it runs standard build tasks like bundle install, rake db:migrate, rake test, etc. All these tasks can be modified to suite the needs of the project.
If the build fails, notifications get sent out to team members, who can easily track the problem in the build log. This way, our team doesn't need to waste time on running all the tests locally. They simply commit the code and get notified only if something goes wrong.
If all tasks run successfully, it can also do automatic deployment to a server you define. We usually have two servers for every project:
- production - the main server and the one used by users
- staging - where we test out features before releasing them to the general population
When we're developing new features, we usually do them in feature branches so we don't mess with the master branch. The master branch needs to always be “deployable”. When we create a new branch, it gets automatically added to Semaphore.
Polishing with CodeClimate
We use one more tool in our workflow. It's not a standard CI tool, but a tool for automated code reviewing, called CodeClimate. It also gets triggered on every code commit and runs a series of code quality checks and security analyses like:
- test coverage
- code complexity
- code smells
- security issues
- potential vulnerabilities
With CodeClimate, our team gets notified about new issues and changes in code quality in real time. This increases the team's awareness of the state of the code and improves overall maintainability in the long run.
We've also integrated CodeClimate with HipChat so we receive notifications directly to HipChat rooms. Since we use Hipchat as our primary communication tool, the fact that Semaphore and CodeClimate are both integrated with it comes in really handy.
You see, when you're developing a complex application, chances are you start getting sloppy. You cut corners, introduce hacks and, in general, stop paying attention to code quality. CodeClimate keeps us in check and stops us from incurring too much technical debt.
A work in progress
With the combination of different tools and techniques mentioned before, we've managed to automate our workflow and drastically reduce the time we spend on testing, building, deploying and debugging our applications. As always, there is still room for improvement, but we are proud of what we've managed to achieve so far.
Please note that it's very important to have a dedicated Continuous Integration server. The reason for that is simple - the nag factor! The CI server, especially if it is integrated with HipChat or a similar communication tool, constantly nags developers to solve the issues that show up with their code. This way, problems get solved as they are introduced - every hour, every day - which is exactly the point of Continuous Integration.
Like this article? Sign up for our monthly newsletter and never miss any of them.