Reduce the runtime of RSpec / Minitest with TestProf

Tram Ho

Many applications have test suites that run exponentially slower as business complexity increases – a problem that is a serious headache for developers about the performance and effectiveness of test cases. In this section, I will show you how I make my test cases run 70% faster by changing a few lines.

Why should we write Test?

Imagine you were given a source code with the test case system that passed all the outputs and its effects. You will certainly be extremely confident to refactor again until you are satisfied with its quality. The benefit of this is that it allows you to code faster, easier and more secure, limiting bugs that can occur after fixing the code. This is extremely important because maintain time usually takes longer than development time and more people are involved in a project, so a fully tested project is extremely important. great snow.

On the contrary, with untested code, it will be a nightmare for any develop or a team. It will slow down or even cause potentially dangerous errors that are not easily detected. So getting rid of this fear is paramount to high-performing teams and companies.

Faith in TDD

You must have heard a lot about TDD, I love TDD more and more. It reminds me of the scientific method – successful results are determined before testing. Therefore, the result will not affect your judgment.

TDD keeps you focused on the important things – the inputs and the outputs , not what’s going on inside. We often change the implementation of a class several times during development by dividing it into small classes, reversing the logic or using different data structures. With TDD, you can be confident that refactor will not change the output. This is why TDD is so powerful.

Problem – The test is too slow

I have completed my test suite – 45 test cases RSpec / Minitest. But they are too slow – running takes more than 25 seconds.

When practicing with TDD, you need to run several times (through the red, blue, refactor cycle). And 25 seconds is a long time to wait for results (this is just a small example). This makes my day a lot more boring. What can I do?

Cause – Why is it so slow?

There are two main reasons why test cases run too slowly:

  1. Active Record: Ruby on Rails makes it very easy to combine DB access inside classes – this results in writing unit / integration tests that are significantly slower than unit tests. These are the tests that you test your class but also create objects in the DB in the process. Accessing the DB is much slower than executing the code, and itself slows down your tests.
  2. Factory cascade: To create such complex objects in test cases, many RoR developers use factory-bot gems. It is a great gem that improves code quality and helps create concise test cases by taking advantage of the Factory pattern. But most likely, it also led to a phenomenon called factory cascade . A factory cascade is when one factory uses other factories, and those factories use other factories as well. In particular, you can easily find yourself performing more than 30 insert DB queries in a single test case. (See this great article to learn more about this topic .)

So I not only created records into the DB in my tests, but I also created a lot of them – and that caused slow.

Solution: Test-prof Gem

There is a cool gem called test-prof . It has a number of tools that can help you analyze your test suite and improve it. There is nothing complicated in my code, so immediately suspect a factory cascade .

To understand the level of factory cascade, I used the factory profiler provided by the gem.

FactoryProf results:

For 45 test cases, 1,490 records were created in the DB, causing these 45 test cases to take more than 15 seconds (about 65% of run time) to create the object. The number of records is terrible! Keep reading below to see how I reduce my audience 70% and runtime.

The first thing I noticed was that I created 152 location records and took four seconds – but none of them were explicitly called from my test suite (top-level was 0).

To stop this, I’ll look for the factory that created locations. The culprit seems to be the invoice factory. Because I built an invoice data management class, you can assume I call the invoice factory quite often.

In case you didn’t know – you shouldn’t use FactoryBot ‘s create method every time you need an object. You should only use it if you need to maintain the object in the DB. There are other options available:

  • FactoryBot.build – It will not create objects in the DB, so it is much faster than create , but it will have associations in the DB. So it may cause factory cascade!
  • FactoryBot.build_stubbed – It will also not create objects in the DB, nor create associations. Pretty. But there is an attraction: It fills in the id column and all the fields are identified in the factory. When using this method, you will not be able to use associations without explicitly declaring them to the factory – for example: FactoryBot.build_stubbed (:location, address: address)

OK, proceed to correct as follows

Take a look at the results I get when using build_stubbed :

See, when stub locations have also greatly reduced the number of address and bank_accounts created. And we have 30% reduced run time.

Next let’s see if we can do anything more?

primary_reservation also seems to be another factor. Again, we see a factory that is not explicitly called but takes a large amount of runtime – eight seconds. Let’s take a look at factory that might be the culprit here:

Aha! It is factory line_item . The items are rows in the invoice – so they used quite a bit in this test set. Find places that don’t need to be used and assign nil values ​​to them:

See results:

Another six seconds were gone. Great, object creation decreased from more than 15 seconds to four seconds – 70% reduction. Four seconds seems fast enough to stop at this point.

Summarizing

  • Be careful when using FactoryBot.create . It can significantly slow down testing.
  • Always try to use build_stubbed whenever possible, and if possible, use build .
  • If create , use test-prof to understand its bad level with test cases and try to optimize it with this information.

Thank you for reading. Article translated from source

Share the news now

Source : Viblo