00:00:16.480
Hi, just getting the mic in order here. I apologize in advance; my slide presentation software seems to not really like this resolution, so we're probably going to be dealing with it all afternoon, but we'll get by.
00:00:34.960
How many people here consider themselves testers? I should say, how many people here use testing in development in any way, shape, or form at any time in their lives? How many people use RSpec? How many people test regularly?
00:00:56.559
I could do this all day, frankly, but okay, what we're going to talk about here is two challenging problems in testing.
00:01:10.560
The first problem is creating data for tests, meaning you want data that’s easy to set up and fairly robust to prevent your tests from failing for trivial reasons. We're going to cover some techniques for doing that.
00:01:20.320
We're also going to talk about limiting tests to a particular part of the code. How many people were in Sandy's talk this afternoon? Almost everybody, that's good. She talked a lot about unit tests in terms of having a test focused on a particular part of the problem.
00:01:30.400
One thing you don’t want is for a test to fail because another part of the code has a problem. You don't want your tests to fail for reasons unrelated to their purpose.
00:01:40.080
Now, it turns out that one reason creating test data is hard is that when there are numerous tests involved, they often have entangled data. For instance, if you want to create a purchase in a unit test, you may also need to create a user, a product, a line item, and other complex object tree stuff.
00:01:55.520
Similarly, limiting your tests to a specific part of the code is challenging when your objects are entangled. If testing my purchase code requires calling various methods that belong to other classes, those are potential breaking points for tests.
00:02:24.959
So, the bright idea here is to keep our objects from being entangled. One way to do that in a Test-Driven Development (TDD) system is by driving the design with tests.
00:02:30.400
Therefore, we'll discuss one major problem: how to drive modular design with tests.
00:02:55.360
Now, a couple of things I'd like to point out at this point. There's a quirk about teaching TDD; I've taught test-driven development for years. One sticking point is that when explaining TDD step by step, taking five minutes to elaborate on each line of code makes it feel very slow.
00:03:01.840
Trust me when I say that strict TDD in practice is much faster, but when you have to explicitly explain every test and every line, we quickly find ourselves 20 minutes in with just three lines of code.
00:03:15.360
And people often wonder what's the point if it takes that long to write just three lines of code. Just remember, while writing it may take time, it will be the most valuable time spent.
00:03:25.280
This example is, of course, just an example. It is purposely missing aspects like security and design elements that would turn it into a full web application.
00:03:39.759
As we know, testing is considered a hallmark of professionalism in this community, so hopefully, you will learn how to do it better.
00:03:50.560
Now, let me get the application running.
00:03:53.520
If you have the app running, it should look a little like this. This is a travel agency that caters to time travelers.
00:04:11.040
The website primarily consists of trips, each with a start and end date, which indicate the time travel dates. Each trip can feature one or more hotel options and multiple activities that users can choose to purchase.
00:04:41.280
What we want to focus on here is users purchasing a trip. The requirement is to unify a user, a trip, a hotel, and a set of days that the user will be staying.
00:04:56.080
We also want to calculate the overall price of the trip, determine if the hotel is available, and run this through a pretend purchase gateway.
00:05:18.240
However, we're not going to worry about the view logic right now.
00:05:27.640
Let’s discuss where we might start with this requirement. Where would you start? What would be the first thing you might consider doing?
00:05:38.000
How about adding a trip to a cart? Let's assume for the moment that it’s just a button and we're just going to go straight to the purchase.
00:05:54.240
We can start from the top with an end-to-end test that covers the entire span of the feature, or we can start from the bottom and build up individual units.
00:06:06.560
The other option is to dive right in, not test, and come back later.
00:06:16.079
What's wrong with diving right in? You might think that it's easy and that you don't necessarily need tests for things you think you understand.
00:06:29.920
Well, the value of testing is that it helps clarify and finalize your requirements. It also assists in driving the design.
00:06:42.560
Sometimes, overseeing something may appear simple but can become complex. You end up having to look closer.
00:06:52.960
Look, it's much harder to build a test into a system that has already been built without tests.
00:07:04.800
So, as we work bottom-up, we need to outline the pieces of the functionality.
00:07:15.560
Specifically, we need to think about calculating the price, validating whether the hotel is available, and validating payments against a pretend gateway.
00:07:31.840
A good starting point would be calculating the price. What’s a good place to begin?
00:07:42.240
Considering both initialized state testing and happy path testing, let’s think about what's possible.
00:07:54.760
You can start with an initialized state test, where you create a new user with no purchases.
00:08:05.280
Then, another good place would be the basic happy path—testing the simplest piece of data that works.
00:08:20.760
It's also advantageous to think about what might make your tests fail and addressing special cases or error conditions.
00:08:29.640
So if you go into the actual travel piece, we might create the most basic way to do this. It’s about as simple a test as I can create.
00:08:44.400
We can clean this up by making sure we’re creating a trip and we're using RSpec to create this trip.
00:09:06.240
We’re going to set up the purchase with that trip.
00:09:25.600
What would be the simplest way to have this pass? Well, just returning the 200 would work.
00:09:42.240
The advantage of returning that value is that it drives quality since we start testing against real values.
00:09:52.000
In practice, you always want to have a failure first to ensure comprehensive testing.
00:10:08.760
Ultimately, if we drive tests incrementally, we build quality into our tests and design.
00:10:17.400
We're also observing the pattern of ensuring tests fail when they should, rather than having hidden issues.
00:10:34.800
Building the correct tests allows us to express intent clearly in the code while also making alterations easier down the line.
00:10:49.600
So we will test for hotel validity based on prices.
00:10:59.360
When creating the stubs for price, we know to pay attention to making clear assertions in our tests.
00:11:11.920
This builds flexibility, as we can adjust to changes in other areas of testing.
00:11:27.440
As we proceed with these tests, we need to keep track of their effectiveness as they adjust with project changes.
00:11:41.760
Best practices entail being careful about stubbing and managing potential complications in test integration.
00:11:59.840
Ultimately, we acknowledge that while approaches like factories and mocks can be powerful, they carry their potential risks.
00:12:10.960
Every time you create associations, it can pile up additional complexity.
00:12:31.440
In conclusion, it helps to break down obstacles blocking an easy path to quality tests by wrapping and structuring correctly.
00:12:44.960
That will lead to the success of your testing workflow.
00:12:54.960
Remember, clarity and simplicity are your friends in maintaining effective tests.
00:12:59.200
Thank you for spending this time with me. I hope you found this valuable!
00:13:08.480
Have a great rest of the conference!