Cookin' with Ruby on Rails - Integration Testsby Bill Walton
NOTE TO READER: This tutorial is intended for use with Rails 1.2.6. It has not yet been updated to support Rails 2.x.
It's just about lunch time and CB's trying to decide whether to hit the cafeteria or go out for lunch. And just in time... in walk Paul and Boss.
CB: Hi, Paul. Hi, Boss. Whatchall been up to? Thought I'd see you sooner.
Paul: Hi, CB. Been fighting fires. I been trying to get over here, but I wanted Boss to come along so we could introduce him to Rails's Integration tests like you suggested. Unfortunately, he's been caught up in the same mess as me. It looks like we've got it under control now, so here we are!
The upside of the last few weeks is that Boss' interest in automated tests probably couldn't be any higher than it is right now. I'm convinced we could have avoided most of this latest mess if we'd been doing the kind of testing you and I've been working on. Even more so if we had automated tests the business users could understand; maybe even write with a little help.
CB: Well then... it sounds like the wait was worth it. Timing is everything! ;-)
Speaking of timing... I was just about to go grab something to eat. Whatdaya think Boss? You up for another pizza?
Boss: No problem, CB. Like Paul said, I'm very, very interested to hear and see more about the tests Paul's been telling me about. I'll be honest though. I'm a bit sceptical about customers being able to really understand automated tests. As I understand it, you and Paul have been writing code; pretty much just like you were writing code for the application itself. Our business partners aren't programmers.
CB: The tests Paul and I've been working on have been at a lower level than the ones we're going to work on today, Boss. The tests we'll be tackling today are known as Integration tests in Rails-speak. I'm pretty sure you'll be surprised at how easy they are to understand. It's an area where Rails really shines. Our Integration tests will be written as a "domain specific language," DSL for short. A DSL is a language that's aimed at solving a specific type of problem. We'll use the Rails test framework to create a DSL to describe the way our application works and how the business uses it; in a language the business users will understand.
Boss: I think you're going to have to show me, CB. I definitely am not imagining the business reacting positively to the notion that we're going to be creating a new language. Some of them already think that we're deliberately, and unnecessarily, using words they don't understand just to make them feel... let's see... what's a good word?... schtupit?
CB: Actually, Boss, what I'm talking about is creating a "programming" language that we'll use to write tests that will very closely mimic the way they think and talk about the application and how they use it. But I think you're right. It'll be easier to show you. You gonna make that call?
Boss: The pie? Absolutely. Why don't you go ahead and get set up while I make the call?
CB has previously put a copy of cookbook2 on his laptop and he's already decided to use both it and the copy on his desktop for this meeting with Boss. He opens a command window on his laptop, moves into the cookbook2 directory and starts Mongrel, and then opens a browser window. On his desktop, he opens another command window and navigates into the cookbook2/test directory, and then opens his favorite text editor.
Boss: Pizza's on its way! Paul tells me you've already created a bunch of tests, CB. You want to show me those?
CB: I could, Boss, but I think to give you a feel for how this would work with the business it'll be better if we start out with the Integration tests. Then, when you want to look under the hood, we can go back to the lower-level Unit and Functional tests. Is that OK with you?
Boss: Sure, CB. But before we start, what do you mean by "lower-level"?
CB: The Rails test framework lets us take a layered approach to testing our applications, where each layer builds on the one below. The lowest layer is the Model, which is responsible for the application's data and the business logic that governs how that data can be used. We use Unit tests to test that our application's Models work correctly. Controllers sit between the application's Models and Views, passing data back and forth and controlling the application flow. Rails calls the tests that verify that a specific controller is functioning correctly Functional tests. Views present data to and collect data from our site's visitors via web pages. As visitors use our app, they'll interact with multiple controllers through multiple views. Rails calls the tests at this top level Integration tests because they integrate all the pieces and allow us to test our application as a whole.
Boss: Interesting. All right, then. Let's get started.
CB: OK then. Paul, would you mind driving the app? What I've got in mind, for today's session, is for you to work through the app with Boss on the laptop while I work next to you on my desktop system and write the Integration tests the two of you come up with. That way, Boss can sit between us and follow along on both fronts. Later, as we move forward with the app and you get comfortable writing the tests yourself, you can work with Boss to prototype our enhancements and write their basic Integration tests, and then you and I can work together to write the app code and the Unit and Functional tests.
Paul: Sure. That makes sense.
CB: Great. I've already started Mongrel on the laptop, Paul. Would you browse over to our application?
CB: Thanks, Paul. That reminds me... we're still starting our app by explicitly invoking either the Recipe controller or the Category controller. We're need to decide what URL we're going to use for our home page. We'll want to visitors to be able to get to our app using a URL like http://www.cookbook2.com. So what do you think, Boss? Do we know what our home page is going to be?
Boss: That's a good question, CB. Right now, I'd say it'll be the Recipe page Paul just brought up. But that could change in the future. Is that going to cause us problems?
CB: Not really, but the more we think it through now, the less rework we'll have to think through in the future. Rails makes it easy to change the page we start with, but if we're not careful our Integration tests could be impacted. Let's go ahead and make the change to the app side and then keep the fact that the home page could change in mind as we add Integration tests. The change to the app side is easy. Rails lets us specify the default page in a configuration file named routes.rb.
All we need to do is make two small changes. As the instruction in the file tells us, we need to delete the public/index.html file from the public directory change. I'll do that first. Paul, would you repoint the browser?
CB: So at this point, the web server doesn't have a static page to render and, when it passes the request to Rails, it doesn't know what to do with it either. So let's uncomment the line in routes.rb that tells Rails what to do if there's no controller specified and point it to our Recipe controller...
map.connect '', :controller => "recipe"
CB: So now we've told Rails what to do with a URL that doesn't specify a controller. Rails now knows that the root action of our application is the index method in our recipe controller. Paul, would you hit the refresh button for us?