Talks

99 Problems of Slow Tests

Recorded in June 2018 during https://2018.rubyparis.org in Paris. More talks at https://goo.gl/8egyWi

Paris.rb Conf 2018

00:00:11.490 I want to introduce you to Vladimir
00:00:14.550 Dementyev. Thank you. Check, check, check.
00:00:24.119 Do you hear me? Cool. Hey everyone, welcome
00:00:28.439 to my talk called '99 Problems of Slow Tests'. But first of all, I'd like to thank
00:00:31.110 the organizers for such a great event and for having me here. So, let me
00:00:36.780 introduce myself. Yeah, it works. So my name is Vladimir. People not from Russia often
00:00:42.180 call me Vlad except for Bozidar, who calls me Lola. I really appreciate it. So, you can find me on Twitter,
00:00:50.070 GitHub, and I have a blog that is probably much more interesting than my Twitter. I have a lot of stuff to share with our
00:00:58.440 community, so please check this out.
00:01:04.170 I actually had a really long trip because I visited a World Cup match in Samara, supporting Team Russia. Unfortunately, they lost.
00:01:13.980 So, I'm here working for a company called Evil Martians. We do product development, but it's mostly boring stuff and non-boring stuff. We're doing a lot of open source projects, and I think some of our most popular projects are made public.
00:01:27.890 We write about everything we do in our blog, so please check it out at least to see our beautiful illustrations.
00:01:31.649 Let's start with the talk. Hmm, my clicker, that's working. Yeah, it works.
00:01:36.119 So, '99 Problems of Slow Tests' is the title, and we have only 20 minutes so that's about five problems per minute.
00:01:43.740 But actually, I'm not going to leave any time for QA sessions, so we have 25 minutes; that's much better. Four problems per minute.
00:01:52.920 So I'll try not to be too fast. Let's start with a single question. Do you think your tests are slow?
00:02:01.619 Yeah, I see some nodding in the audience, and I believe you already know why we should care about test performance.
00:02:11.070 We talked about it yesterday in the opening keynote, and I don't want to repeat everything right now since we don't have enough time.
00:02:18.000 Let's talk about my personal story. When I started to care about tests, I worked on a project called Fontan.com, and we didn't have that many tests.
00:02:27.890 However, the test suite runtime was around half an hour, and the speed was less than 2200 tests per minute, which is unacceptable.
00:02:35.500 It led us to the situation where we spent a lot of time waiting for our CI builds to finish or while running local tests, which severely hurt our feedback loop.
00:02:44.420 So, I spent some time optimizing aspects, and we ended up with a much better situation. While not the best yet, we reached a point where we could run a sound contest per minute, which was okay.
00:02:58.690 As a result of this optimization work, a project called TestProbe appeared. What is TestProbe? In a couple of words, it's a wizard that helps you analyze your test suite.
00:03:06.720 It's a collection of multiple tools which helps you to analyze your test suite performance and fix issues.
00:03:16.780 There are two groups of tools: profilers and custom recipes, and I'm going to show some of them. I'll be talking about the test suite problems.
00:03:24.670 So let's start with the first problem, a very common one that I call a misconfigured environment.
00:03:29.899 We have different environments in most applications, especially Rails applications: test environments, production environments, and development environments.
00:03:42.789 They all differ in some settings, and the way you configure your test environment could affect your tests' runtime.
00:03:56.790 I'm going to show you some examples, but let's first talk about how to diagnose such problems. The best way to do that is actually to use general Ruby profilers, like RubyProf, StackProf, and the new one, RubySpy.
00:04:07.240 They are not so useful for analyzing the performance of tests since they are not test-specific; they're general.
00:04:12.710 But we can still gain interesting insights from them. For example, the first one is from an engineering blog that shows us we spend a lot of time on log write operations in our tests.
00:04:25.290 The question is, why are we writing to the log in our tests at all? There is no reason for that, so we can just turn it off to make our tests faster.
00:04:36.180 Another example is a little bit more complex. Here we see that we have a bit of creep in our library, where 20% of the time is spent within certain methods.
00:04:55.900 It turns out that we had encryption settings in our authentication library that were as strong in the testing environment as in production.
00:05:10.100 This doesn't make sense, so we relaxed the setting, and our tests became more than three times faster just because of this change.
00:05:17.620 These are just a few examples of how misconfigured environments can make our tests slow, so it's vital to care about your test environment.
00:05:25.990 Next problem: Database cleaner. We need this to handle multiple tests, especially with Rails 4 and earlier, because Active Record uses one connection for threads.
00:05:35.680 But this is not the case since Rails 5.1 thanks to Arel, and if you're using Rails 5.1 or later, this becomes an issue.
00:05:46.709 Basically, the feature exists, but most of us cannot use it. TestProbe provides an alternative patch called ActiveRecordSharedConnection, which used to be called ActiveRecordOneLaf.
00:06:02.430 Let’s measure the impact of having a database cleaner. We can use the EventProbe profiler from TestProbe to examine our runtime.
00:06:11.599 With the database cleaner enabled for a number of tests, we see that we spend 30% of the time on the database cleaning.
00:06:20.710 However, with the shared connection, we spend no time cleaning, which decreased my test suite runtime by 30%.
00:06:30.950 If you're using database cleaner, you should examine if you need it, as in most cases you probably don't, and removing it will make your tests faster.
00:06:44.929 Next up, let's talk about an important testing feature that is often overlooked: handling inline jobs.
00:06:56.350 Many people default to using Sidekiq in inline mode, where every job is executed immediately, but that's not always ideal.
00:07:03.310 In fact, most of your tests do not rely on jobs, which leads to unnecessary overhead and slower test execution.
00:07:15.750 For example, in our test suite, using Sidekiq testing in inline mode resulted in about a quarter of the execution time being spent executing jobs.
00:07:28.560 We decided to disable this mode for tests that do not rely on jobs, and the result was significantly faster tests.
00:07:38.810 That's a strong example of how tweaking default settings can positively impact performance.
00:07:47.130 The key takeaway here is that inline jobs should not be the default setting for tests unless required, so evaluate your needs.
00:07:58.960 Moving forward to a new feature in TestProbe: monitoring method calls.
00:08:05.800 This can be beneficial because sometimes adding new dependencies can slow down your test suite, especially if they don't provide testing utilities.
00:08:14.490 So, if you add a new library, please pay attention to how it affects the performance of your tests. One common example is 'PaperTrail'.
00:08:25.760 You might find it causes delays due to the creation of versioning records in your ActiveRecord models.
00:08:38.119 Fortunately, PaperTrail offers a way to disable versioning by default, allowing you to enable this feature only when needed.
00:08:53.460 Make sure to check this out when using PaperTrail to ensure you are not wasting time on unnecessary versioning.
00:09:04.170 Next, I want to touch on a common topic: the old adage of 'do not use create, use build'. It's a well-known topic among developers.
00:09:19.130 I don't want to cover it in depth, but the TestProbe includes a profiler known as 'FactoryDoctor'.
00:09:28.260 It helps detect instances where you can replace create calls with build calls, which can significantly improve performance.
00:09:40.960 While it's not foolproof and can produce some false positives or negatives, it often works quite well.
00:09:54.250 As an additional tool, there's a patch called 'FactoryLifters' that simplifies replacing create calls without needing manual changes.
00:10:07.590 Okay, back to EventProbe. Another critical event we should analyze is the 'FactoryCreate' event, which measures the time spent generating test data.
00:10:24.150 I've often seen that data generation in factories can take up to 60-80% of the total test runtime.
00:10:39.390 This isn’t because factories are bad; it’s typically due to poorly engineered factory designs.
00:10:45.590 One common problem is known as 'FactoryCascade', where creating a single object inadvertently creates multiple dependent objects.
00:10:55.070 This can be quite inefficient, leading to a slower test suite execution time.
00:11:04.970 To better illustrate this, we can visualize the 'FactoryStack', which is a representation of calls made during one top-level object creation.
00:11:16.830 Analyzing these stacks can help identify which factories create the largest cascades, leading to inefficiencies in the tests.
00:11:29.830 I had a real-life case where our test suite slowed down significantly after a small commit due to factory cascades.
00:11:42.130 Using EventProbe, I found that just two specs generated about 90% of the excessive time spent in factories.
00:11:53.850 From there, I analyzed the factory stack and managed to streamline it. After making these changes, the performance improved significantly.
00:12:05.690 It's vital to ensure that your test factories are efficient and to avoid unnecessary cascading during the object creation process.
00:12:17.350 Let’s also discuss a design principle about minimizing repeated setup work in your test code.
00:12:30.590 If you find yourself with lengthy before hooks because of setup work across multiple test examples, consider refactoring to reduce duplication.
00:12:42.260 One suggestion is relying more on 'before' and using 'let', which creates variables that can be reused throughout the examples.
00:12:58.160 Reducing setup times can have a significant impact on overall test performance. Consider how many tests can share setup data.
00:13:10.700 Try to employ shared setups when possible to avoid repetitive creation of the same data.
00:13:22.920 Staying with the theme of code efficiency, utilize helper libraries where appropriate to simplify test setups.
00:13:33.650 Keep an eye on how data is generated repeatedly throughout your test suite. This is crucial for maintaining fast test suites.
00:13:46.420 Next, let’s discuss why it’s important to have a mindful approach to assertions in your tests.
00:13:58.160 Tests with multiple assertions can take longer to process if they require recreate data for every assertion.
00:14:06.870 Respond to this by considering utilizing aggregate failures to keep the execution streamlined and efficient.
00:14:19.170 If multiple assertions can check distinct elements of the same data set, consolidate their usage to avoid redundant computations.
00:14:28.990 So, another way to enhance performance is the consideration of shared fixture data instead of generating new data for each test.
00:14:46.480 If you correctly utilize fixtures, it can drastically cut down your test suite run times.
00:14:54.910 Something to ponder is the balance between factories and fixtures. Both have their advantages, so utilize them based on your needs.
00:15:08.220 As we proceed, we should target the elimination of repetitive data generation amidst loops and refactor our approaches.
00:15:20.690 We are approaching our ninth problem discussed so far. As a common pitfall, many developers neglect their code's performance.
00:15:30.190 Ensure performance testing is integral to your process throughout all stages of development, especially in CI/CD.
00:15:39.290 As a recap of the problems addressed, remember the importance of monitoring merges and how they can affect CI/CD workflows.
00:15:51.670 In summary, it's vital to maintain a solid grasp of your test suite's structure and performance metrics as part of your development process.
00:16:03.370 I believe that we've covered an expanse of insights today.
00:16:11.970 If you have further questions or require assistance, please feel free to reach out after the talk or during breaks.
00:16:24.310 Thank you for your attention and I hope these insights into preventing slow tests help you expedite your workflows.
00:16:36.370 Thank you very much, and if you want some of those stickers, feel free to find me.
00:16:51.780 Thank you.
00:16:54.370 Fred Amiel here, any questions?
00:16:57.560 Right, thank you.
00:17:07.790 We usually end up having slow tests and when it's too late, do you have any pointers to prevent that?
00:17:15.030 Thank you for the question! I'm exploring the idea of building CI helpers for TestProbe to monitor your tests during builds and collect stats to alert any issues.
00:17:30.150 It’s quite an open-source project idea I have in mind, although time constraints are a factor. However, I'm hopeful to bring it to fruition.
00:17:45.330 Just ensure to monitor the performance of your tests continuously to prevent heavy slowdowns during integration.
00:17:56.300 Any other questions?
00:18:03.730 Alright, you can catch me at the after-party if you have questions.
00:18:05.360 Thank you!