3 tips để cải thiện performance test suite của bạn

Chúng ta, những Rails developers luôn luôn lo lắng về việc cải thiện hiệu suất của các bộ test trong ứng dụng. Hôm nay tôi sẽ chia sẽ 3 lời khuyên mà tôi sử dụng trong dự án của mình để cải thiện tốc độ test của bạn.

1. Giảm thông số Devise.stretches

Thêm phần dưới đây vào ffile spec/test helper của bạn:

Devise.stretches = 1

Thông số này là thời gian, chi phí số lần mà password của bạn được mã hóa, và Devise mặc định nó là 11.

Giải nghĩa: Devise mặc định sử dụng bcrypt-ruby để mã hóa cho password. Bcrypt là lựa chọn tốt nhất cho công việc như vậy bởi vì khác với các thư viện băm khác như MD5, SHA1, SHA2 nó được thiết kế thông qua nhiều lớp bảo vệ (hay còn gọi thiết kế làm chậm lại) . Cho nên nếu một người nào đó lấy cắp database (họ chỉ có thông tin của database, còn việc họ làm việc được trên database của mình thì sẽ không) của bạn họ sẽ phải mất một thời gian dài để crack mỗi mật khẩu trong đó.

Điều đó có nghĩa rằng, những test case liên quan đến Devise sẽ chậm hơn trong quá tình thực hiện test case bởi vì rất nhiều test case cần phải tạo ra vào so sánh mật khẩu cho mỗi lần tạo user. Vì lý do này, một cách đơn giản để tăng hiệu suất test là giảm thông số stretches của Devise xuống mức thấp nhất có thể. Điều này làm mật khẩu của bạn kém an toàn nhất nhưng không sao vì nó chỉ được áp dụng trên môi trường test mà thôi.

Phiên bản Devise mới đã cập nhật ưu điểm này và set stretches bằng 1 cho môi trường test được set trong file config, nhưng nếu hệ thống của bạn đã từ lâu và dùng phiên bản devise cũ thì bạn nên điều chỉnh nó để có hiệu suất tốt hơn.

2. Tăng log level lên

Thêm phần dưới đây vào file spec/test helper của bạn:

Rails.logger.level = 4

Giải nghĩa: Mặc định Rails sẽ ghi log tất cả những gì đang xảy ra trong môi trường test của bạn vào file log/test.log. Bằng cách tăng logger level lên bạn sẽ có thể giảm IO trong quá trình test. Nhược điểm của phương pháp này là nếu test case fail, bạn sẽ không thể biết được bất cứ thông tin gì về việc vì sao nó fail, vì nó không có log nữa. Và trong trường hợp này, bạn comment out cái phần trên và chạy lại test case bị fail đó để xem được log của nó.

3. Sử dụng shared connection với transactional

Nếu bạn đang sử dụng Capybara cho javascript test và Active Record thì hãy thêm những dòng dưới đây vào file spec/test helper của bạn và chắc chắn rằng bạn đang chạy với transactional bằng true.

class ActiveRecord::Base
  mattr_accessor :shared_connection
  @@shared_connection = nil

  def self.connection
    @@shared_connection || retrieve_connection
  end
end

# Forces all threads to share the same connection. This works on
# Capybara because it starts the web server in a thread.
ActiveRecord::Base.shared_connection = ActiveRecord::Base.connection

Giải nghĩa: Một thời gian dài trước đây, khi Rails vẫn còn ở những phiên bản 1.x, một tùy chọn cho configuration mới là use_transactional_fixtures được add vào Rails. Tính năng này rất đơn giản, trước mỗi test case Active Record sẽ đưa ra một tuyên bố bắt đầu transaction và phát hành một rollback sau khi các test case được thực thi. Nó là tuyệt với bởi vì Active Record sẽ đảm bảo rằng không còn dữ liệu nào ở lại trong Database của chúng ta bằng cách sử dụng transactions một sự lựa chọn thực sự nhanh chóng.

Tuy nhiên, phương pháp này có thể không làm việc với tất cả các trường hợp. Active Record connection pool hoạt động bằng cách tạo ra một collection mới vào database cho mỗi thread. Và mặc định database connection không chia sẽ transactions state. Nó có nghĩa rằng, nếu bạn tạo dữ liệu bên trong một transaction trong một thread (nó có kết nối cơ sở dữ liệu của chính nó), thì thread khác không thể nhìn thấy bất kì dữ liệu nào được tạo ra. Điều này thường không phải là vẫn đề, trừ khi bạn sử dụng Capybara với Javascript tests.

Khi bạn sử dụng Capybara với Javascript tests, Capybara bắt đầu ứng dụng Rails của bạn bên trong một thread để các trình duyệt cơ bản (như Selenium, Webkit, Celerity, etc) có thể truy cập vào nó. Khi test suite và server chạy ở trong những thread khác nhau, nếu test suite chạy bên trong một transaction, tất cả database được tạo bên trong test suite sẽ không được thấy bên trong server (server sẽ không gọi được nó ra). Ngoài ra, server nằm bên ngoài transaction, data được tạo bởi server sẽ không được làm sạch sau khi test thực thi xong.Vì lý do này, rất nhiều người đã tắt tính năng use_transactional_fixtures và sử dụng các function Database Cleaner để clear database mỗi sau mỗi lần thực thi test (cụ thể như những config Model.destroy_allđược config trong rsec helper). However, this affects your test suite performance badly. Tuy nhiên, điều này cũng có ảnh hưởng làm performance của bạn xấu đi.

Với tình hình như trên, ta sẽ cung cấp một giải pháp đơn giản để giải quyết cho cả 2 vấn đề. Buộc Active Record chia sẽ cùng một connection cho tất cả các threads. Nó không phải là vấn đề trong bộ test suite của bạn bởi vì khi test thread đang chạy thì sẽ không có một request nào từ server thread. Khi server thread chạy thì test thread sẽ đợi phản hồi từ server trả về. Vì vậy, sẽ không có trường hợp cả hai cùng sử dụng connection tại cùng thời điểm. Vì vậy, bạn không cần phải sử dụng Database Cleaner (trừ khi bạn sử dụng một cơ sở dữ liệu khác như là Mongo). Và điều quan trọng là bạn phải bật use_transactional_fixtures bằng true để tạo ra transaction bao bọc cả test suite và server gúp cải thiện hiệu suất test cho bạn.

Cuối cùng, nếu có một phần của mã code của bạn sử dụng threads để truy cập vào database và bạn cần phải test chúng, bạn chỉ cần set ActiveRecord::Base.shared_connection = nil trong suốt quá trình test trường hợp đặc biệt đó và mọi thứ sẽ làm việc tốt đẹp.

Trên đây là những tips cung cấp cho bạn. Rất mong các bạn sẽ thấy thú vị với nó.

.


All Rights Reserved