Test Driven Development (TDD) trong Ruby on Rails

TDD là gì?

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.

Tác dụng của TDD

  • 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.

Những điều luật khi áp dụng TDD

  • 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.

Ví dụ cụ thể

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.

Các lỗi thường gặp khi áp dụng TDD

  • 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

Kết Luận

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 !!!