Working Effectively with Legacy Rails Code - RailsConf

Rails has powered, a site generating tens of millions of dollars in revenue for the past two years.

It has 80 models, an equal number of controllers, 75k lines of code…and no tests. I will talk about how we’ve been chipping away at growing an effective, valuable test suite since I’ve come on with Oakley.

Likewise, Rails has powered for over a year and now handles every request to the site (around 10 million unique visits a month). It has 60 controllers, 250 models, and a test suite that takes 6-8 minutes to run every time. On top of this, the Rails app still uses MediaWiki as a webservice, so it depends on an unknown number of lines of PHP which is not covered by any tests in the PHP code base. We will talk about how we are switching to Rspec and Cucumber, brought online a CI process, and how we manage code that may not be “The Rails Way”.

“Where the hell do I start?” That’s the question people ask when they first toy with the idea of adding tests to an existing application. We certainly can’t stop development and spend weeks just writing tests. How do we maximize the value we get from time spent adding tests? Writing high-level acceptance tests that step through complete usage scenarios, are perfect for casting a big net and getting a lot of regression coverage.

We write tests to cover existing code so that we can add new features and fix bugs. When working on a piece of untested code, we must spend time analyzing the possible consequences of our changes. From there, we can write an acceptance test to cover the broad area, and then write unit tests for various objects and methods that we touch. Then we can test-drive the new feature or bug fix.

Maddox’s Law: 99% of untested code is untestable code. I’ll talk about mindful refactorings that you can perform without the safety net of tests, in order to get the code to a point where you can test it.

To keep the scope of our testing and changes small, we need to be able to isolate dependencies in code. There are many techniques for identifying and introduces seams into your code, which allows you to isolate or break dependencies. I will also discuss how Ruby’s dynamic nature enables powerful techniques that aren’t available to programmers in other languages.

Continuous integration is extremely valuable when you’re building a test suite. It allows you to run the slower acceptance tests asynchrously rather than slowing down your development workflow. Setting up CI might be a challenge in its own right, given that testability was not built into the system. At Oakley, simply checking out the source code and setting up an app is an ordeal, with lots of migration scripts in different locations, external dependencies, and hidden knowledge such as storing default values in the db at a certain ID. I’ll talk about some of the challenges we’ve faced and how we’ve been able to make gradual improvements.

In addition to the technical challenges, politics are another common roadblock to introducing automated testing. How do you sell your boss on taking some extra time to test? How can you get the other developers in your organization on board with a testing strategy? How do you responsibly begin testing when you don’t have much experience with it?

Most people have a superficial understanding of how valuable testing can be, but many people are facing too much pressure at work to feel confident in getting started. There’s a lot of discussion about TDD and how great it is, but in the real world we frequently have to deal with lots of code that has not been tested at all. How can we take a mass of untested code that runs our businesses, and start working to the ideal of high-quality, malleable, tested code, while still meeting the demands of high productivity in our daily work? My talk will get people thinking and talking about that process, and give them practical techniques for making it reality.