00:00:24.480
Hi everyone. My name is Jillian, and I'm going to be talking about black box testing with RSpec and Capybara. I'll be using examples in Capybara and RSpec, but a lot of this is applicable to testing in general or web application UI testing.
00:00:30.800
So, a quick introduction to what Capybara is: it's a DSL for testing web applications that simulates how a real user would interact with your application. It has methods like fill_in and click_button. One of the common methods you'll see is find, where you pass in a CSS selector, and it returns a Capybara element you can then interact with, such as clicking it or getting its text or value.
00:00:45.680
It also has a range of predicate methods that are useful in RSpec, such as has_content, has_css, has_link, and has_button. I'm going to go through an example using a simple registration form, and I'll describe what the HTML for it looks like. This registration form is for a shopping app.
00:01:21.520
Your first attempt at a spec for this might look like this: you're registering a user using Capybara's methods: visit, fill_in email, fill_in password, click_button save, and expect there not to be an error. This is pretty readable for a single page, and if this were the only page in your application, it might be sufficient. However, as new specs are added, like editing a user profile, we quickly find ourselves repeating the same steps to register a user.
00:01:56.079
For example, we need to visit the registration form, fill in the email, fill in the password, and save it. Then, we move on to the profile editing test. If you keep adding more specs, the code can get cluttered quickly with all the repetitive registration steps. This can make it look bad and, worse, obfuscates the purpose of your tests.
00:02:21.760
The reason this kind of repetition is common in test code is that we often think of tests as accessories to our code rather than actual code themselves. Poorly written tests can lead to the same issues we see with bad application code. These tests are not DRY (Don't Repeat Yourself), making them harder to read and maintain, especially since any UI changes require modifications across multiple tests instead of just one place.
00:02:58.239
When you review your test code, you might find similar lines repeated at the top of every test. It's important to remedy this repetition, and one effective solution is using the page object pattern. Martin Fowler has an excellent blog post about this concept. The idea is to define a class for each page that encapsulates the logic for interacting with that page.
00:03:10.880
This approach makes it easy to reuse methods whenever you need to interact with that page. Furthermore, if there's a change to the page interface, you only need to update the corresponding page object, rather than changing multiple tests. This also enables optimization of slow tests, as you can quickly modify performance-related logic in one place rather than scouring the entire test suite.
00:03:58.240
Here's an example for the registration page we just looked at. It includes the Capybara DSL and stores the URL for the page, calling Capybara's visit method without needing to explicitly pass in the URL. The class contains a register method that encompasses those familiar steps: visit, fill in the email, fill in the password, and save. We've also wrapped the CSS for the expected error handling, including a has_no_flash_error method for easy reuse.
00:04:37.919
Now our test reading looks a lot cleaner. We initialize a registration page and simply call register, expecting there to be no flash error. This way, we've abstracted away the CSS selectors and UI elements, allowing us to focus on the logic of the test itself. If changes are made to the page, we only need to modify the page object, leaving our test cases unchanged.
00:05:03.680
Similarly, the user profile spec is now much clearer: it starts with user registration, making it evident what’s happening at the top. You don’t need to parse through multiple lines of code to discern what's going on. This consistency helps maintain readability. As you create more page objects for different components, your tests will become simpler and less redundant.
00:05:49.200
Next, you’ll want to consider making page objects for other pages, like the edit profile page or the order history page. The shared logic among these pages will lead to more concise and maintainable code. For instance, aspects such as URL handling and common methods for navigating pages can be abstracted into a more generic structure.
00:06:16.639
What if you have pages that share common elements but are not identical? In that case, you may consider creating form objects that encapsulate the fields and actions you want to perform. This includes fields for first name and last name and methods to fill them in. This reusable functionality across different forms minimizes code duplication and enhances clarity.
00:06:58.560
In implementing this, you can include Forwardable and delegate method calls for these fields to your form object. By doing this, you simplify your page objects' logic as they will inherently have access to the necessary functionality, further streamlining the registration or editing processes. This approach allows your tests to operate smoothly with simple and clear syntax.
00:07:23.520
We've discussed how page sections can be shared among multiple pages and how to handle similar elements that aren't identical. For instance, if you have dialog boxes that pop up, you can create a dedicated class for those dialogs to harness shared behavior. That way, your code structure remains organized, allowing for easy adaptability as new UI components are introduced.
00:08:10.720
Sometimes, you might have situations where some forms have similar characteristics but do not exactly match each other in structure. To tackle this effectively, you could define a base class for your forms, which allows for variation while preserving common functionalities among similar elements. This design will keep your page objects neatly categorized while promoting reusability.
00:08:51.120
Now, regarding the implementation of custom methods, it's crucial to ensure that you're not directly inheriting from Capybara's find method for your specialized classes, as this could lead to confusion and mismanagement of the underlying elements. Instead, consider utilizing delegation patterns to ensure you can accurately handle and customize element behavior on a case-by-case basis.
00:09:35.920
In conclusion, utilizing the page object pattern enhances the readability and maintenance of your tests. If your specs are laid out logically, new team members can easily grasp the structure and focus on code quality without getting tangled in repetitive logic. Streamlined code helps you avoid redundancy, letting you improve your test set without making sweeping changes.
00:10:13.280
I hope today's talk inspires you to rethink how you structure your tests, treating them as first-class code rather than an accessory to your application. This could offer you valuable benefits in the long run, especially as your codebase grows. The cleaner and more organized your tests are from the start, the better your team can leverage them as the application evolves.
00:10:50.720
Thank you for your time and attention today, and thank you to the organizers for hosting this talk. I would also like to acknowledge Eric and Glenn for their contributions to the materials I presented today and encourage everyone to explore the resources provided here for more in-depth knowledge.
00:11:06.160
Lastly, if you want to connect with me, here’s my Twitter handle. My blog also has additional content on these topics, and I welcome any feedback on the material or thoughts on your experience implementing similar patterns.
00:11:26.320
As we wrap up, remember that well-structured tests can significantly improve your team’s efficiency and effectiveness. Don't hesitate to implement these ideas even in a small codebase because, over time, the complexity can grow, and you'll appreciate having a solid foundation for your tests.