Test Driven Development (TDD) trong Ruby on Rails
TDD (Test Driven Development) là một phương thức làm việc, hay một quy trình viết mã hiện đại. Lập trình viên sẽ thực hiện thông qua các bước nhỏ (BabyStep) và tiến độ được đảm bảo liên tục bằng cách viết và chạy các bài test tự động (automated tests). TDD còn được hiểu là quá trình Red - Green - ...
TDD (Test Driven Development) là một phương thức làm việc, hay một quy trình viết mã hiện đại. Lập trình viên sẽ thực hiện thông qua các bước nhỏ (BabyStep) và tiến độ được đảm bảo liên tục bằng cách viết và chạy các bài test tự động (automated tests). TDD còn được hiểu là quá trình Red - Green - Refactoring liên tục trong quá trình phát triển phần mềm. Hiểu đơn giản hơn thì TDD sẽ chạy đúng theo quy trình sau:
- Viết 1 test cho hàm mới. Đảm bảo rằng test sẽ fail.
- Chuyển qua viết code sơ khai nhất cho hàm đó để test có thể pass.
- Tối ưu hóa đoạn code của hàm vừa viết sao cho đảm bảo test vẫn pass và tối ưu nhất cho việc lập trình kế tiếp
- Lặp lại cho các hàm khác từ bước 1.
- Hiểu được requirement rõ ràng hơn khi nhận specs.
- Giảm thiểu được bugs trong quá trình phát triển cũng như bảo trì sau này.
- Giảm công sức cho QA, giảm thiểu thời gian test lại sau mỗi lần nâng cấp sản phẩm.
- Không cho phép viết bất kỳ một mã chương trình nào cho tới khi nó làm một test bị fail trở nên pass.
- Không cho phép viết nhiều hơn một unit test mà nếu chỉ cần 1 unit test cung đã đủ để fail. Hãy chuyển sang viết code function để pass test đó trước.
- Không cho phép viết nhiều hơn 1 mã chương trình mà nó đã đủ làm một test bị fail chuyển sang pass.
Nói thỳ phức tạp, để dễ hiểu hơn chúng ta đi vào xây dựng phát triển một ví dụ cụ thể. Specs của ví dụ này là viết một hàm check xem tham số truyền vào có phải là số nguyên tố hay không. Chúng ta sẽ xây dựng với quy trình đúng theo tư tưởng TDD
1. Xây dựng một class với function check số nguyên tố trống
class TddNumber class << self def prime? num end end end
Hàm trên có tên là prime? nhận vào một tham số và check xem tham số đó có phải là số nguyên tố không.
2. Xây dựng rspec theo tư tưởng TDD trước
Hàm prime? sẽ chỉ có thể trả ra kết quả là true hoặc false nên chúng ta sẽ xây dựng hai case test với hai trường hợp trên.
require 'rails_helper' RSpec.describe TddNumber do describe '::prime?' do it 'true' do expect(TddNumber.prime?(7)).to eql(true) end it 'false' do expect(TddNumber.prime?(8)).to eql(false) end end end
Có thể thấy hai case test trên cho hai trường hợp số truyền vào là 7 hoặc 8 tương ứng với hai trường hợp true và false điều này hiển nhiên đúng.
3. Chạy thử rspec trên
rspec spec/lib/tdd_number_spec.rb
Cho kết quả như sau:
Failures: 1) TddNumber::prime? true Failure/Error: expect(TddNumber.prime?(7)).to eql(true) expected: true got: nil (compared using eql?) # ./spec/lib/tdd_number_spec.rb:6:in `block (3 levels) in <top (required)>' 2) TddNumber::prime? false Failure/Error: expect(TddNumber.prime?(8)).to eql(false) expected: false got: nil (compared using eql?) # ./spec/lib/tdd_number_spec.rb:9:in `block (3 levels) in <top (required)>' Finished in 0.02067 seconds (files took 4.97 seconds to load) 2 examples, 2 failures Failed examples: rspec ./spec/lib/tdd_number_spec.rb:5 # TddNumber::prime? true rspec ./spec/lib/tdd_number_spec.rb:8 # TddNumber::prime? false
Lỗi là đúng do chúng ta chưa viết code gì trong hàm prime?
4. Xây dựng code trong hàm prime?
class TddNumber class << self def prime? num is_valid = true for i in 2..Math.sqrt(num) if num%i == 0 is_valid = false break end end is_valid end end end
Bây giờ chạy rspecs sẽ thấy được kết quả pass hết nếu code chúng ta viết đúng.
Finished in 0.00478 seconds (files took 3.89 seconds to load) 2 examples, 0 failures
5. Refactor code sao cho chạy đúng được rspec đã viết
class TddNumber class << self def prime? num is_valid = true (2..Math.sqrt(num)).each do |i| is_valid = false if num%i == 0 break end is_valid end end end
Khi đó chạy lại rspecs vẫn pass như lúc trước.
Finished in 0.006 seconds (files took 4.12 seconds to load) 2 examples, 0 failures
Như vậy từ bây giờ dù có refactor code thế nào đi nữa cũng cần pass qua tất cả rspec chúng ta đã viết lúc trước mới đảm bảo không ảnh hưởng tới các phần khác.
- Không quan tâm đến các test bị fail
- Quên đi thao tác tối ưu sau khi viết code cho test pass
- Thực hiện tối ưu code trong lúc viết code cho test pass => không nên như vậy
- Đặt tên các test khó hiểu và tối nghĩa
- Không bắt đầu từ các test đơn giản nhất và không theo các baby step.
- Chỉ chạy mỗi test đang bị fail hiện tại
- Viết một test với kịch bản quá phức tạp
Thiết kế dựa trên kiểm thử (TDD) là một kỹ thuật phát triển, trong đó trước tiên bạn phải viết một mã kiểm thử chạy thất bại, trước khi bạn viết mã nguồn cho chức năng mới. TDD đang nhanh chóng được nhiều nhà phát triển phần mềm theo phương pháp Agile chấp nhận để phát triển mã nguồn ứng dụng, và thậm chí còn được thông qua bởi những nhà quản trị cơ sở dữ liệu theo phương pháp Agile (Agile DBA) cho phát triển cơ sở dữ liệu. TDD nên được xem như là bổ sung cho phương pháp phát triển hướng mô hình Agile (Agile Model Driven Development – AMDD) và cả hai có thể được sử dụng cùng nhau.
TDD không thay thế phương pháp kiểm thử truyền thống, thay vào đó nó định nghĩa một cách thức để đảm bảo việc thực hiện các unit test một cách hiệu quả. Hiệu ứng phụ của TDD là các kiểm thử cung cấp một đặc tả hoạt động cho mã nguồn. TDD được đánh giá tin cậy trong thực tế và được nhiều lập trình viên phần mềm quan tâm và lựa chọn.
Thanks for reading !!!