Вечера в феврале длинные, дошли руки до того, что давно откладывал. Rails 8.0, Ruby 3.4, около 4100 примеров на RSpec, в CI набор идёт 14 минут 20 секунд, локально 11. Гадать надоело, повесил подписку на события factory_bot и посчитал, сколько раз и сколько по времени создаётся каждая фабрика:
# spec/support/factory_stats.rb
FACTORY_STATS = Hash.new { |h, k| h[k] = [0, 0.0] }
ActiveSupport::Notifications.subscribe("factory_bot.run_factory") do |event|
key = "#{event.payload[:name]}:#{event.payload[:strategy]}"
FACTORY_STATS[key][0] += 1
FACTORY_STATS[key][1] += event.duration
end
at_exit do
FACTORY_STATS.sort_by { |_, (_, ms)| -ms }.first(5).each do |key, (count, ms)|
puts format("%-24s %7d %9.0f ms", key, count, ms)
end
end
Результат: user:create вызывается 31 тысячу раз, хотя примеров 4100. Почти каждый create(:order) тащит за собой пользователя, компанию и ещё одного пользователя-менеджера через association. Время вложенных фабрик считается дважды, так что точный процент не назову, но по грубой оценке на фабрики уходит больше половины прогона. Дальше bundle exec rspec --profile 20 показал, что самые медленные группы те, где let! создаёт по десять заказов на каждый пример, а нужны они в двух примерах из пятнадцати.
let! на десять заказов это классика жанра. Посмотрите ещё, где можно build_stubbed вместо create: в тестах на методы модели, которые не ходят в базу, разницы никакой, а запись в базу не пишется вообще. В спеках сериализаторов и презентеров обычно половину можно перевести без потерь.
14 минут на 4 тысячи тестов. на Go набор такого размера проходит секунд за сорок, просто к слову
Со стороны CI: набор можно разбить на 4 параллельных джоба по времени выполнения файлов, а не по их количеству. Если сохранять время каждого файла из прошлого прогона, части выходят ровными, и 14 минут превращаются в 4-5 плюс время на подготовку окружения.
Отчитываюсь, две недели по вечерам. У заказа убрал association :manager, менеджер теперь создаётся только там, где он нужен. В 40 спеках сериализаторов create заменил на build_stubbed. В трёх самых медленных группах let! поменял на let и создаю данные внутри тех примеров, которым они нужны. user:create упал с 31 тысячи вызовов до 9400. Локально набор теперь 5 минут 10 секунд, в CI на одном джобе 6:40. Параллель ещё не включали, это следующий шаг. По флеймграфу около 40 секунд уходит просто на загрузку приложения перед первым тестом, но это уже отдельная история.
сохранил себе скрипт с подпиской, интересно, что он покажет на моём наборе