00:00:16.800
Welcome to RailsConf. I am Brian Sam-Bodden, a Rails, Ruby, and Java consultant. I run a small company in Scottsdale, Arizona, and I'm here to talk about RSpec and Capybara.
00:00:24.880
Testing is one of those subjects that in most frameworks and languages we kind of take for granted. It's like, oh, it's the stuff that we do at the end of the show. The Rails community actually took that and flipped it on its head. I believe one of the earliest versions of Rails had a built-in testing framework that showed that you could automate web testing.
00:00:38.239
In this talk, I'm going to discuss practical behavior-driven development. We're going to talk about test-driven development (TDD) and I'm going to try not to be dogmatic about it. One of the things that drove me away from testing early on was the overly religious attitude some people had around it. It just felt really strange, almost cult-like. However, once I got into the practice of it, I began to see the value. I'm going to share some of the practices that I use in my daily work and how TDD with RSpec and Capybara can help you build better applications.
00:01:10.360
I want to make you comfortable with the tools, RSpec and Capybara, specific to this talk and the practices we'll cover today. You're going to need Git. I do have a backup zip file, so if you don't have Git, we can probably try that as well. That link is actually the same as the PDF link, just with 'zip' at the end, a wonderfully lengthy URL for you to type.
00:01:31.159
You'll also need RVM; I do have an RVM.rc file in the application you'll be working with that creates a gemset for you. Let me see a show of hands—how many of you are familiar with Git? This is awesome! How about RVM? Great! Ruby? Good. If you have all that, then we're good to go. We're going to be cooking with fire.
00:01:48.960
I don't really care what text editor you use, as long as you have a decent keyboard. We have 80 minutes to cover RSpec and Capybara. I'm going to start with RSpec, then we'll jump into Capybara for 40 minutes each. I'll mix lecture with some coding that you can follow along with, so hopefully you're ready to write some code. Not too much code, but enough! I have two images on the slides to indicate whether I want you to follow along with me—look for the green 'follow along' sign. We'll have two labs, two very simple labs, that we'll do at the end of each of the 40-minute sessions.
00:02:59.680
Let's talk about testing: unit testing, integration testing, acceptance testing, behavior-driven development, and test-driven development—lots of acronyms! In the Agile Manifesto, this statement stood out to me when I was correlating testing with the quality of our applications: 'Our highest priority is to deliver the most value to the customer.' Value does not mean documentation; it does not mean anything other than runnable, testable, reproducible behavior and code.
00:03:30.840
That's what our customers are looking for, and that is the direction that the practices in the Rails and Ruby communities have taken development toward. Let me see how many of you came from a Java background? Okay, how about C or C++? When I was working in C and C++, testing was often the last thing on our minds. We just ran through the code and did our testing visually, hoping for the best. When someone changed something, the application became trickier to verify as it grew more complex.
00:04:14.760
Testing, in the waterfall methodology, was something done at the end. Most projects had a deadline—flags in the sand with a calendar date that we aimed for—and testing often got squeezed out. Most languages lacked strong frameworks for testing. It all started with Smalltalk, but really took off with Java and JUnit, and since then, testing frameworks have sprouted up in nearly every language.
00:04:39.200
In the Ruby world, we had Test::Unit from the get-go. I'll be showing RSpec, which is now an evolution of Test::Unit, allowing us to focus on behavior-driven development. One issue with unit testing frameworks is that we tend to use them for every aspect of testing—from integration to acceptance and user testing—leading to a dilemma about where to start. We often start with unit tests because that's what we learn first, but it's not necessarily where we get the most value from testing.
00:05:13.400
This was a problem I encountered a lot in the Java world. We often had thoroughly tested models or business objects, yet the controllers remained a disaster. It was pretty difficult, even with well-tested model layers, to test the integration between the controllers and the models, and forget about the views; they often felt like a warzone. Behavior-driven development (BDD) aims to reverse our tendency to focus on the smallest units tested in isolation and instead to start from the outside in.
00:06:07.360
Many BDD testing frameworks have evolved over the years, and by far my favorite is RSpec. There are RSpec clones in almost every language, similar to how Rails emerged in other languages. One thing I mentioned before is that integration testing is quite challenging, especially in the middle layers of the application—between controllers and models or between views and controllers. What we aim to do with BDD is start at the view and complement those tests at the model level, leaving the complexities in the middle as 'the wild west' until the application stabilizes.
00:06:53.679
Testing in the middle areas can become costly because those tests are generally fragile and brittle, especially as the application evolves. We once held a mantra of achieving 100% code coverage; however, we paid for that dearly as what seemed to be a 100% coverage application one month could drop to 40% code coverage the next with 60% of tests broken that no one wanted to refactor.
00:07:39.280
Let’s see how many of you work with Rails on a daily basis or aspire to do so? That's a significant majority. Just to refresh your memory: in Rails, we have traditional request handling. I refer to that as the reverse weatherman—it looks odd in video too. We want to focus our outer layer testing on the view, and as we do so, we remain compliant in our assumptions about the inner layers of the application until we reach the model level. The two ends of this equation should agree.
00:08:23.919
I put together a little diagram of what I think the process should look like. At the top, the first step is to write a failing acceptance test that mimics what the user does—entering data into fields, pressing submit, and then expecting to see something happen like a popup or a flash message. This type of testing resides at the outer layer of the application. I call that acceptance testing because it lies between your application and the user, serving as a checklist for user approval, ensuring it behaves as expected.
00:09:23.480
However, not every test at this layer becomes an acceptance criteria-bound test; some are edge cases that users might not care about, but we do, as we want to ensure our application works reliably. Between the views and the controllers in this example, I have one view perhaps talking to two controllers. I do TDD here to help flesh out the controllers, mainly because I want to understand their functionality. However, the acceptance test that I began with still drives the overall process.
00:10:06.440
The same principle applies when I work on the models; one controller might be using multiple models, and at the model layer, I'll design the API using TDD. If we look at the right side of the diagram, you can see an arrow going down to acceptance testing, thus creating a big outer loop. Inside, we have mini loops testing integration between controllers and models, with tiny TDD loops for the individual models.
00:10:51.240
Now, let's briefly discuss BDD and why we refer to it as behavior-driven development. When I first started doing TDD and unit testing, I couldn't really see the difference between the two; I often thought, 'If I'm using Test::Unit, is it unit testing, and if I use RSpec, is it integration testing?' But that’s not the case—what's essential to focus on is what matters to the user, their perceptions of the application.
00:11:31.599
Behavior-driven development (BDD) elevates TDD to maximize value for stakeholders. That might sound like jargon, but what it really means is that you want to test how users experience your application. Sometimes you won't even know what those tests will look like until you observe user interactions. Designers will create a UI they believe users need, but as the design evolves from discussions, testing becomes crucial. Once they have a refined workflow in their minds, that's when we engage with our Capybara acceptance-driven testing.
00:12:52.840
BDD also implies going from the outside in and refining the language into something users can understand. In BDD, we use specifications, and in RSpec, the spec part represents specifications. Once again, how many of you are using RSpec to test your software? That's quite a few of you, so we can probably accelerate through this.
00:13:54.080
One interesting thing is that sometimes it feels strange when using a framework like RSpec for fleshing out a model. You may wonder if it's too removed from the user; however, the framework still allows us to write unit tests in a cleaner manner. So, what to test? Typically, you'll want to reflect user stories or, in some cases, scenarios of use cases. You usually start from the outermost layer, which in web applications is most likely a web page, often a web form, and you want to ignore everything else.
00:14:44.720
I emphasize ignoring the middle because those elements tend to be in flux until the design of the whole application solidifies. We often formulate user stories that outline: 'As a [role], I want to [accomplish something] so that I can benefit in some way.' With RSpec and most BDD frameworks, this translates directly to statements like: 'Given some state, when an action happens, I want to benefit by receiving specific data from the application.'
00:15:36.640
Now we're about to kick off a tutorial focused on TDD with RSpec, centered at the unit level. I don't want you to be misled here; I want you to grasp the mechanics of RSpec. It seems most of you have used RSpec before, so we might be able to move quickly through this. Just to provide you some background on RSpec, it is by far the most popular BDD framework for Ruby. It has been around for a while now, growing past its toddler stage and it provides a DSL that allows you to test in the BDD language.
00:16:36.560
Another advantage of RSpec is that many different frameworks integrate with RSpec, providing matchers that enable expressions in the BDD language. Here’s a quick example of what an RSpec specification looks like. I have an example group at the top for the Describe block. Inside, I have specific examples or specifications. In this case, I'm describing a score in bowling—specifically, it returns zero for a gutter game—and down below, I have an expectation.
00:17:30.280
We set some state, produce an event or action, and then verify what the specification validates. In this case, I'm expecting the bowling score to equal zero. For those of you familiar with RSpec, the 'spec' syntax is fairly new, likely within the last year and a half. You may have seen the older 'should do' notation, but 'spec' seems to be the new standard. RSpec provides various matchers that cover almost every type of comparison imaginable, from booleans to checking for the existence of elements.
00:18:39.479
Additionally, it creates a DSL over specific methods you have; for example, if you have a method called 'exists?,' you can say 'expect to exist.' There's a wealth of useful collection methods and regular expression matchers as well, which come in handy for web application testing.
00:19:50.480
So let’s get into TDD with RSpec. TDD is not only about testing. Whenever I hear that phrase, I wonder, 'What do you mean it's a testing framework?' The key takeaway is that when you’re uncertain about what a class should do, TDD can help clarify your design. TDD is primarily about design. Not every test you write will focus solely on design; some may arise from collaborations or design presumptions you already have. Ultimately, TDD leads to cleaner code and better separation of concerns.
00:20:33.560
Every time I write something without adhering to TDD, I end up with significantly more code than necessary. One common tendency among programmers is to over-abstract and simplify. Someone mentions needing a method in the controller, leaves, and returns to find I’ve accidentally developed a library. TDD helps curve that tendency to over-engineer by keeping us focused.
00:21:21.360
Many people describe TDD as a zone that you enter into, especially when you have it down well. I've been doing TDD for about ten years now, and I’m no expert—I find days when I feel like I can't TDD at all. Instead, I code directly in IRB or the Rails console and then write a test afterward. That’s fine! The Rails console is a great environment for experimenting and identifying how to frame your tests.
00:22:02.680
It's easy when working in IRB; you don't commit any of the code to anything, avoiding developing legacy code on the fly. Working in a transient, carefree manner allows us to experiment and understand how to implement tests better. TDD makes development goal-oriented, which benefits you as a consultant, particularly when interacting with customers.
00:22:56.680
In TDD, we follow a Red-Green-Refactor loop. It starts with failure; this is probably the only instance where starting out from failure is a good thing! Often, I frame the initial test, see it fails, and that’s a good sign. Green is about achieving that next state; however, getting to green without overdoing it takes practice. Once you have the minimal implementation that satisfies the test, refactoring means making it better.
00:23:49.919
During the refactoring phase, new ideas may arise from your code improvements, but don’t code for them yet. Write tests instead, and loop back to the TDD cycle with these new insights in mind. Now, for this follow-along session, I want you to create a new directory. It can be called whatever you like; mine is called 'rspec_follow_along.' After creating this, navigate into that directory and create an RVM file.
00:24:38.360
If you're using any Ruby version manager or gemset manager, perform the equivalent operation you normally would. I'm also creating a Gemfile with a simple content structure, declaring the RSpec gem under the 'test' group. Once you have this in place, you can execute the bundle command, and you might need to install bundler if it isn’t already on your system.
00:25:48.920
After installing bundler, you can bundle it all together, and you should have a directory ready to play with. If you get stuck, please feel free to take a peek over someone’s shoulder; we don't have much assistance available today, so try not to step away or hesitate to seek help from those nearby.
00:26:35.360
Now let me see a show of hands—who’s created the directory? Getting close? Awesome! If you're using RVM, you'll need to CD out of the directory and back in to recognize the RVM file. Following that, run 'bundle' to install the necessary gems. You may see some dependencies that RSpec will pull in, and my version of RSpec is currently 2.8.0, which I believe is the latest version.
00:27:12.720
Let’s start our Red-Green-Refactor loop with the simplest possible failure. We're going to build a mini shopping cart in pure Ruby. Begin with the 'cart_spec.rb' file, which should include a describe block that specifies a 'Cart' class, but let’s leave it empty for now.
00:28:05.440
There are numerous ways to approach developing a shopping cart, but we’ll keep things simple for now to familiarize ourselves with RSpec. Once you have the cart spec in place, run the test by calling RSpec. You probably won’t need to specify the 'spec' path explicitly; just typing 'rspec' should work, provided that you have no cart class; you will receive the simplest of failure messages.
00:28:47.840
As a rule of thumb, I don’t always begin from the simplest failure when implementing tests. Sometimes, I fast forward through a couple of TDD cycles in my mind. The key here is to avoid overshooting, as doing so could cause you to lose clarity on dependencies that should be in check. Initially, it's acceptable to fast forward, but expect to need to account for that later.
00:29:35.680
When I receive a failure without any tests, it signifies that a test is missing to cover that failure. In the next section, let’s create the empty Cart class that aligns with the described block from our spec. Make sure to require relative paths for the Cart class.
00:30:34.760
Now that we have set up our empty cart class and defined the spec, execute RSpec again and expect to see zero failures. However, we should also note that there are still no examples being run despite receiving no errors. I want to ensure that the absence of a test failure is spoken clear across the system, so let me add an example to clarify.
00:31:29.520
Let’s tackle our first realistic test. The 'spec' will verify what occurs when we create a brand new cart. Notice that I'm introducing an extra 'context' block where I define an expectation: a new cart contains no items. I’m setting this expectation on the instance variable '@cart', which should ideally return empty—this leads to a design decision immediately.
00:32:19.280
Assumptions regarding this design are critical; I've preset a design decision that the cart will respond to the 'empty?' method, and therein lies the testable notion. If you run RSpec now, you'll see we have one example and one failure, indicating that the 'empty' method is non-existent.
00:32:56.960
We are inching closer to having something concrete in the application. One appealing aspect of TDD is how this micro-process allows you to think more organized as you build. The iterative design process is particularly clear when working on outer layer tests.
00:33:40.640
Now, to progress, we need to write the minimal amount of code necessary to pass our test. With BDD, it's essential to get your wording right because those words reflect your intent in the code. If not, poorly designed names may lead to confusion or incorrect refactoring.
00:34:32.200
As you notice, the output we receive from RSpec reads, 'A new cart contains no items,' which doesn't flow well. That’s where the beauty of RSpec comes in—there’s a chance to express code in free-flowing strings, making the output readable.
00:35:25.040
Next, we implement the simplest change needed to pass the test: introduce an empty method returning nil to our cart class. Subsequently, we will adjust this method to instantiate the cart instance with initial setup.
00:36:07.680
We can do this by creating an 'initialize' method for our cart class, establishing an instance variable '@items' as an empty hash. Additionally, we will refactor the empty method to delegate to the hash's built-in empty method. This lightweight approach allows us to meet RSpec’s specifications with minimal code.
00:36:43.200
After implementing this, run RSpec again and verify that we now have two passing tests, a true indicator we’re building software diligently and thoughtfully. Observing the Green state is a significant TDD milestone. Once you reach this state, it's time to refactor and seek improvements, as you may identify numerous ideas for enhancements.
00:37:43.720
Early refactorings may involve minor tweaks, but as we advance to integration testing, including acceptance testing from an outsider perspective, the interactions between various tested elements may grow increasingly intertwined, requiring more intricate adjustments and refactorings.
00:38:38.880
In summary, the first step for this session is to create a directory per your preference, like 'rspec_follow_along.' From there, navigate into the directory, set up an RVM file if applicable, and create a Gemfile with the RSpec gem. Following the setup, run bundler to pull in all necessary dependencies.
00:39:32.720
If you do hit any bumps along the way during setup, don't hesitate to glance at your neighbor's screen for assistance since we may not have enough assistance allocated today. Please do remember, also, to create some kindness and patience in facilitating help.
00:40:25.920
After setting up, verify you've created your RVM file properly for recognition, and return back to your directory to run 'bundle'. Upon successful bundle completion, you’ll be prepared to proceed with RSpec exercises.
00:41:27.680
Let's re-engage with the tutorial process by creating a redo of the mini shopping cart in Ruby. Start by constructing a 'cart_spec.rb' containing a described block for the Cart class. It may remain void for now, but at this stage, run RSpec to test for its absence.
00:42:16.080
In the upcoming phase, after identifying the test flow, adapt it to incorporate expected behaviors around the elements inside our Cart class. Prepare to put the processes in motion where you'll explore looping in established tests and clarifications in the next stages.
00:43:19.040
If you run your tests again, you'll witness how behavior-driven development differs in its scope, focusing around interactions primarily illustrating success cases in predictable pathways towards user engagement.
00:44:14.320
As we conclude, rest assured that all examples will be available online via GitHub at the close of the session. Also, I encourage you to evolve your learning beyond today by implementing BDD elements within your workflow as you conceptualize a full-stack application.
00:45:12.040
After we've collaborated in executing easy RSpec configurations, anticipate steering into more complex scenarios involving Capybara where user interfaces and interactivity will come to life. These components should bug resolution and enhancement later on.
00:45:53.520
During the final leg of our session, you'll be called to leverage Capybara's browser automation features. Recognize how to execute test scenarios targeting user experience while interpreting their behavior within your applications.
00:46:58.080
Next, we won't shy away from scripting various interactions for frontend components and user pathways to observe behaviors wherein you may want specific segments of your application tested through practical demonstrations.
00:47:53.760
Now let’s emphasize the importance of treating your application as a black box during your acceptance testing, utilizing Capybara’s features for interaction simulations. Be sure to script the broad-spectrum experiences provided by users when they experience the application through various pathways and designs.
00:48:55.200
That approach ensures the lines between user expectations and application behaviors remain intact. Validation against user stories and pleasant interfaces ought to maintain their integrity through proper behavioral analysis as you're employing straightforward Capybara capabilities.
00:49:49.360
During future exploration, we can treat Capybara as a tool to simulate and validate those experiences at their core. For every action your users will take, ensure to develop a prototype of testing pathways that mimic their operations for a success-driven site.
00:50:46.520
To wrap up, encourage extensiveness in your design processes while navigating Capybara’s testing scenarios. Ensure persistent engagement with active testing techniques to preserve usability and positive perceptions of your application.
00:51:43.760
As you continue processing through your natural testing flows, logical constructs will allow easy adaptations into Capybara. Provide solid implementations, documenting your user paths freely, which will ultimately help enhance experiences while facilitating ease of modifications.
00:52:45.680
Into the future, feel free to connect issues into clear use case scenarios into your Capybara pathways and documentation to develop a robust, user-centered approach to your testing philosophies.
00:53:45.920
As closing endeavors settle on applying these strategies back at home, a team effort through shared libraries and input will yield results, facilitating new models for user engagement strategies to build a better RSpec and Capybara alignment.
00:54:45.680
In closing, be diligent and persistently expand your understanding around these BDD and testing paradigms. Let your visions bridge toward development pathways as you prepare suggestions for your next steps centered around RSpec and Capybara.
00:55:44.600
Questions? Please feel free to share as we gear into further discussions or clarify about any element we may have missed through today’s engagements. Thank you all very much for the time and insights shared in this session.
00:56:50.480
Once again, I appreciate everyone for joining. Remember to explore beyond the knowledge discussed today, take thorough notes, and always seek to expand upon your existing learnings. Good luck with your endeavors going forward!
00:57:43.680
Make use of collaborative networks, enhancing discussions around standard methodologies that can be utilized across practice to remain sharp in consistently developing applications further down the journey. Thank you!