Learn the First Best Practices for Rails and RSpec
Khi chúng ta code Sample App hoặc code 1 dự án for fun 1 2 thành viên chúng ta rất hay bỏ qua bước viết test vì chúng ta cảm thấy project của chúng ta chẳng thêm mới được gì hay thậm chí tự cảm thấy mình xử lý logic chuẩn không cần chỉnh nữa rồi. Nhưng khi tham gia 1 dự án nghiêm túc với khách hàng ...
Khi chúng ta code Sample App hoặc code 1 dự án for fun 1 2 thành viên chúng ta rất hay bỏ qua bước viết test vì chúng ta cảm thấy project của chúng ta chẳng thêm mới được gì hay thậm chí tự cảm thấy mình xử lý logic chuẩn không cần chỉnh nữa rồi. Nhưng khi tham gia 1 dự án nghiêm túc với khách hàng và hoạt động theo team đông người thì việc viết test là vô cùng quan trọng để giảm thiểu tối đa cá thiếu và sai sót trong logic xử lý. Sau đây mình sẽ trình bày cách viết Rspec đơn giản cho một ứng dụng Ruby on Rails
Rails Application
rails new myapp
Installing RSpec
Chúng ta sẽ thêm gem rspec-rails trong group development và test trong Gemfile
group :development, :test do gem 'byebug' gem 'rspec-rails', '~> 3.4' end
chạy bundle:
bundle install
Và generate rspec:
rails generate rspec:install
Câu lệnh trên sẽ tạo ra 1 vài file như sau:
Shoulda-Matchers and Database Cleaner
Chúng ta sẽ thêm 2 gem shoulda-matcher và database_cleaner tại group test trong Gemfile:
group :test do gem 'shoulda-matchers', '~> 3.0', require: false gem 'database_cleaner', '~> 1.5' end
Sau đó chạy bundle install
Shoulda-Matchers Configuration
Shoulda Matchers cung cấp cách viết 1 dòng lệnh Rspec dùng để test các chức năng của Rails. Nó giúp bạn viết test 1 cách ngắn gọn, rõ ràng và ít gặp lỗi hơn
Chúng ta sẽ thêm những dòng sau vào spec/rails_helper.rb
require 'shoulda/matchers' Shoulda::Matchers.configure do |config| config.integrate do |with| with.test_framework :rspec with.library :rails end end
Database Cleaner Configuration
Database Cleaner giúp bạn làm sạch database giữa các lần chạy thử. Chúng ta config như sau: spec/rails_helper.rb:
config.use_transactional_fixtures = false
Tạo thư mục support trong thư mục spec
mkdir spec/support
Trong đó tạo 1 file mới tên database_cleaner.rb và thêm nhưng dòng sau:
RSpec.configure do |config| config.before(:suite) do DatabaseCleaner.clean_with(:truncation) end config.before(:each) do DatabaseCleaner.strategy = :transaction end config.before(:each, :js => true) do DatabaseCleaner.strategy = :truncation end config.before(:each) do DatabaseCleaner.start end config.after(:each) do DatabaseCleaner.clean end end
Vậy là chúng ta đã config xong việc clean database giữa các lần test
Capybara Setup
Capybara là một khung tự động hóa được sử dụng để tạo các thử nghiệm chức năng, mô phỏng cách người dùng sẽ tương tác với ứng dụng của bạn.
Thêm gem capybara vào group :development, :test trong Gemfile, cùng group với gem rspec-rails
gem 'capybara', '~> 2.5'
và chạy bundle install Sau đó vào spec_helper.rb require gem capybara
Faker and Factory Girl Setup
Faker rất hữu ích trong việc tạo dữ liệu ngẫu nhiên cho thử nghiệm của bạn. Thêm gem faker vào group :test trong Gemfile
gem 'faker', '~> 1.6.1'
Factory Girl cho phép bạn tạo các đối tượng mà bạn cần trong các thử nghiệm có thể bao gồm các giá trị mặc định. Với faker, bạn sẽ có thể tạo các đối tượng ngẫu nhiên cho thử nghiệm của mình thay vì chỉ sử dụng một giá trị mặc định.
Thêm gem factory_girl_rails trong group :development, :test
gem 'factory_girl_rails', '~> 4.5.0'
Sau đó bundle install Gemfile của bạn sẽ trông dư này:
source 'https://rubygems.org' gem 'rails', '4.2.4' gem 'sqlite3' gem 'sass-rails', '~> 5.0' gem 'uglifier', '>= 1.3.0' gem 'coffee-rails', '~> 4.1.0' gem 'jquery-rails' gem 'turbolinks' gem 'jbuilder', '~> 2.0' gem 'sdoc', '~> 0.4.0', group: :doc group :development, :test do gem 'byebug' gem 'rspec-rails', '~> 3.4' gem 'factory_girl_rails', '~> 4.5' gem 'capybara', '~> 2.5' end group :development do gem 'web-console', '~> 2.0' gem 'spring' end group :test do gem 'shoulda-matchers', '~> 3.0', require: false gem 'database_cleaner', '~> 1.5' gem 'faker', '~> 1.6.1' end
Creating a Factory
Tạo 1 folder tên factories trong folder spec đồng thời tạo 1 file cùng tên với 1 model của bạn. Ví dụ: contacts.rb
***spec/factories/contacts.rb*** FactoryGirl.define do factory :contact do full_name { Faker::Name.name } email { Faker::Internet.email } phone_number { Faker::PhoneNumber.phone_number } address { Faker::Address.street_address } end end
Model Specs
Chúng ta muốn đảm bảo rằng Factory chúng ta tạo ở trên là hợp lệ cho model. Tạo một tệp trong thư mục spec/model cho model của bạn:
***spec/models/contact_spec.rb*** require 'rails_helper' RSpec.describe Contact, type: :model do it "has a valid factory" do expect(contact).to be_valid end end
Trong terminal bạn chạy lệnh:
bundle exec rspec spec/models/contact_spec.rb
Nó sẽ hiện lỗi vì chúng ta chưa có model Contact. Nên chúng ta cần phải tạo:
rails g model Contact full_name:string email:string phone_number:integer address:text rails db:migrate
à nếu bạn chạy lệnh đầu tiên nó sẽ bắt ghi đè file spec/factories/contacts.rb trong terminal bạn nhớ chọn "No" nhé
Chúng ta sẽ sử dụng shoulda-matchers để kiểm tra presence của full name, email, phone number và address nên trong contact_spec.rb các bạn thay
RSpec.describe Contact, type: :model do it "has a valid factory" do expect(contact).to be_valid end end
bằng
describe Contact do it { is_expected.to validate_presence_of(:full_name) } it { is_expected.to validate_presence_of(:email) } it { is_expected.to validate_presence_of(:phone_number) } it { is_expected.to validate_presence_of(:address) } end
Sau dó chạy lại bundle exec rspec spec/models/contact_spec.rb
nó sẽ bị lỗi vì trong model chúng ta không yêu cầu nó phải presence. Để sửa chúng ta chỉ cần thêm trong models/contact.rb như sau:
class Contact < ActiveRecord::Base validates_presence_of :full_name, :email, :phone_number, :address end
Và kết quả nhận được như sau: Vậy là đã pass!
RSpec DSL Pieces
RSpec là một DSL để tạo các ví dụ thực thi về cách mã được dự kiến sẽ hành xử, được tổ chức theo nhóm.
describe tạo ra một nhóm ví dụ. Nó cần một đối số cho biết thông số kỹ thuật là gì. Đối số có thể là một lớp, mô-đun, phương thức hoặc mô tả chuỗi.
it tạo ra một example và lấy một mô tả về example đó
expect cho phép bạn thể hiện kết quả mong đợi trên một đối tượng trong một ví dụ
Controller Spec
Controller spec sẽ thực hiện test nếu 1 new contact được tạo ra với các valid attributes
Chúng ta generate controller:
rails g controller Contacts
Nó sẽ đồng thời tạo luôn cho chúng ta spec/controllers/contacts_controller_spec.rb. Trong đó chúng ta thêm test action create như sau:
require 'rails_helper' RSpec.describe ContactsController, type: :controller do describe "POST #create" do context "with valid attributes" do it "create new contact" do post :create, contact: attributes_for(:contact) expect(Contact.count).to eq(1) end end end end
Chúng ta thêm trong config/routes.rb
***config/routes.rb*** ... resources :contacts
Và trong app/controllers/contacts_controller.rb:
class ContactsController < ApplicationController def new @contact = Contact.new end def create @contact = Contact.new(contact_params) respond_to do |format| if @contact.save format.html { redirect_to @contact } format.json { render :show, status: :created, location: @contact } else format.html { render :new } format.json { render json: @contact.errors, status: :unprocessable_entity} end end end private def contact_params params.require(:contact).permit(:full_name, :email, :phone_number, :address) end end
Run spec:
bundle exec rspec spec/controllers/contacts_controller_spec.rb
Theo spec ở trên, hãy viết một spec sử dụng các thuộc tính không hợp lệ để tạo một contact mới. Spec này cần kiểm tra rằng liên hệ không được tạo:
***spec/controllers/contacts_controller_spec.rb*** context "with invalid attributes" do it "does not create a new contact" do post :create, contact: attributes_for(:invalid_contact) expect(Contact.count).to eq(0) end end end
Tổng kết
Tài liệu them khảo: https://www.sitepoint.com/learn-the-first-best-practices-for-rails-and-rspec/