- vừa được xem lúc

TDD qua ví dụ thực tế

0 0 47

Người đăng: Huy Tran

Theo The Full Snack

TDD qua ví dụ thực tế

TDD (Test Driven Development) - tức là một phương pháp lập trình chú trọng vào việc test, "viết test trước viết code sau",... rất nhiều người đã thử tìm hiểu về TDD và đều đọc được những định nghĩa như thế này trong các bài viết, nhưng cuối cùng khi đọc xong thì vẫn không hiểu nổi TDD là gì.

Một trong những câu hỏi được đặt ra nhiều nhất khi nghiên cứu về TDD có lẽ là: Áp dụng TDD như thế nào trong thực tế?

Chuẩn bị hành lý

Rõ ràng khi bắt đầu code thì phải setup những thứ như là cấu trúc thư mục blah blah. Nhưng phần này không liên quan đến TDD, nên mình sẽ nói lướt qua. Nếu các bạn không hiểu thì có thể hỏi trong phần comment nhé.

Tạo một thư mục dành cho project của chúng ta, có thể đặt tên tùy ý, trong đó tạo 3 file lần lượt như sau: Rakefile, FizzBuzz.rbFizzBuzz_test.rb

Rakefile có nhiệm vụ định nghĩa task chạy test, có nội dung như sau:

require "rake/testtask" Rake::TestTak.new(:test) do |t| t.libs << "test" t.test_files = FileList["*_test.rb"] t.verbose = true
end task :default => :test

Chúng ta sẽ implement module FizzBuzz, trong module này có 1 hàm run() nhận vào tham số N và trả về các giá trị như đã nói ở phần khái niệm. Đây là phần sườn code cho module FizzBuzz, nằm trong file FizzBuzz.rb

module FizzBuzz extend self def run(n) end
end

Phần code test sẽ đặt trong file FizzBuzz_test.rb và có nội dung như sau:

require "minitest/autorun"
require "./FizzBuzz" class FizzBuzzTest < Minitest::Test end

Sau khi tạo xong 3 file trên, các bạn có thể chạy thử test bằng lệnh:

rake test # hoặc là rake

Output sẽ như thế này:

# Running: Finished in 0.001125s, 0.0000 runs/s, 0.0000 assertions/s. 0 runs, 0 assertions, 0 failures, 0 errors, 0 skips

Output như trên nghĩa là bạn đã cài đặt thành công chương trình, nhưng chưa có test case nào được chạy.

Giờ chúng ta sẽ bắt tay vào phần chính, test và code.

Chờ chút!

À, mà trước khi bắt đầu thì cũng nên nhắc lại một tí về TDD. TDD tức là "viết test trước khi viết code". Nghĩa là sao? Chưa có code thì làm sao mà test?

Đây chính là mấu chốt, khi bạn định implement một function nào đó, bạn sẽ phải viết một function khác, sử dụng chính cái function bạn định implement, và khi chạy test tất nhiên nó sẽ fail, đơn giản là vì bạn chưa implement cái gì cả. Vì vậy việc tiếp theo là implement để làm cho nó hết fail (pass). Cuối cùng, bỏ chút thời gian ra refactor lại code cho đẹp và gọn gàng hơn, để lỡ có bị ai đọc vào thì họ cũng khôi có chửi được mình =))))

FizzBuzz_test.rb

def test_fizzbuzz_run_return_fizzbuzz expect = "FizzBuzz" actual = FizzBuzz.run(15) assert_equal expect, actual
end

Chạy cho nó fail nào...

# Running: .F. Finished in 0.001382s, 2170.7670 runs/s, 2170.7670 assertions/s. 1) Failure:
FizzBuzzTest#test_fizzbuzz_run_return_fizzbuzz [...]:
Expected: "FizzBuzz" Actual: "Fizz" 3 runs, 3 assertions, 1 failures, 0 errors, 0 skips
rake aborted!

OK lỗi rồi. Giờ implement nào, mà khoan, sao lần này giá trị actual không phải nil mà là Fizz nhỉ. Thôi kệ. Bỏ qua.

...Viết code sau

Vậy là giờ chúng ta phải implement cho trường hợp cùng chia hết cho cả 35. Làm như thế nào nhỉ?

FizzBuzz.rb

def run(n) if n % 3 == 0 return "Fizz" elsif n % 5 == 0 return "Buzz" elsif n % 3 == 0 && n % 5 == 0 return "FizzBuzz" end
end

Ngon lành chưa? Hmm, hình như có gì đó ko đúng lắm, thôi kệ, chắc lo xa quá thôi, test nào.

# Running: .F. Finished in 0.001588s, 1889.1688 runs/s, 1889.1688 assertions/s. 1) Failure:
FizzBuzzTest#test_fizzbuzz_run_return_fizzbuzz [/Users/huy/Desktop/Code/rbfizzbuzz/FizzBuzz_test.rb:20]:
Expected: "FizzBuzz" Actual: "Fizz" 3 runs, 3 assertions, 1 failures, 0 errors, 0 skips
rake aborted!

Ớ, vẫn lỗi. Sao kì zẩy, rõ ràng là fail nè. Implement rồi mà fail là sao ta. Xem nào, nó fail vì actual trả về "Fizz", kì lạ... Có gì đó sai rồi, cùng xem lại code nào.

Đầu tiên chúng ta check coi số n có chia hết cho 3 không n % 3 == 0, sau đó check coi n có chia hết cho 5 không n % 5 == 0. À đúng rồi, vậy là nó hốt luôn cả 2 trường hợp chia hết cho 3 và 5 rồi, cho nên trường hợp cùng chia hết cho cả 3 và 5 thì không thể nào mà nằm ở dưới cùng như vậy được.

Vậy cách giải quyết là ngay từ đầu chúng ta phải kiểm tra trường hợp cùng chia hết cho 3 và 5 trước.

FizzBuzz.rb

def run(n) if n % 3 == 0 && n % 5 == 0 return "FizzBuzz" elsif n % 3 == 0 return "Fizz" elsif n % 5 == 0 return "Buzz" end
end

Đó, làm vậy mới đúng, giờ chạy test lại coi nào:

# Running: ... Finished in 0.001329s, 2257.3363 runs/s, 2257.3363 assertions/s. 3 runs, 3 assertions, 0 failures, 0 errors, 0 skips

Đó! Pass rồi thấy chưa? Vừa rồi không phải là tui gà đâu nha, tui cố tình sai cho mấy bạn khỏi bị mất tập trung đó nhớ đó nha, không phải do tui gà đâu đó

Test case #4: Nhận vào một số không chia hết cho 3 hay 5 gì cả, trả về chính số đó

Test case cuối cùng này thì nhẹ nhàng thôi, không có trap gì giống như khúc trên nữa đâu, ứ yên tâm mà test.

Viết test trước...

Trong trường hợp này, input có thể sẽ là 2, 4, 8 gì đó, miễn là nó không chia hết cho thằng nào trong 2 số 3 và 5 là được.

FizzBuzz_test.rb

def test_fizzbuzz_run_return_n expect = 8 actual = FizzBuzz.run(8) assert_equal expect, actual
end

Test sẽ fail, nếu không fail thì cần phải xem lại

# Running: ..F. Finished in 0.001451s, 2756.7195 runs/s, 2756.7195 assertions/s. 1) Failure:
FizzBuzzTest#test_fizzbuzz_run_return_n [/Users/huy/Desktop/Code/rbfizzbuzz/FizzBuzz_test.rb:26]:
Expected: 8 Actual: nil 4 runs, 4 assertions, 1 failures, 0 errors, 0 skips
rake aborted!

...Viết code sau

Rồi, giờ thì implement cho trường hợp cuối cùng, code nhanh còn đi cà phê chớ. Mình có cái tật đang code mà ai rủ rê đi cà phê uống nước là bực lắm, tại nghe rủ là đứng dậy đi liền à...

FizzBuzz.rb

def run(n) if n % 3 == 0 && n % 5 == 0 return "FizzBuzz" elsif n % 3 == 0 return "Fizz" elsif n % 5 == 0 return "Buzz" else return n end
end

Test luôn:

# Running: .... Finished in 0.001397s, 2863.2785 runs/s, 2863.2785 assertions/s. 4 runs, 4 assertions, 0 failures, 0 errors, 0 skips

Toẹt zời! Pass hết luôn rồi!!!

Kết luận

Đến đây, các bạn đã implement xong một module FizzBuzz hoàn chỉnh, với đầy đủ mọi test case cần thiết để đảm bảo module luôn chạy đúng và không xảy ra bug tiềm ẩn. Cho nên trong quá trình sử dụng module này trong dự án, nếu có xảy ra lỗi thì các bạn có thể chạy lại test để đảm bảo vấn đề không nằm trong này. Vậy là tiết kiệm được một chút thời gian khi debug.

Các bạn có thể tham khảo source đầy đủ của module FizzBuzz trong bài tại đây


Để áp dụng TDD một cách hiệu quả thì bạn cần phải nắm rõ được yêu cầu, chia được vấn đề cần xử lý thành các vấn đề nhỏ hơn (từng test case), điều này đòi hỏi bạn phải tốn một khoản thời gian ban đầu để suy nghĩ về việc mình cần làm trước khi thực sự bắt tay vào code. Nhưng lợi ích mà nó đem lại thì rất nhiều, giống như việc bạn đi vào rừng mà không sợ bị lạc vì đã có sẵn tấm bản đồ ngồi tự vẽ từ đêm hôm qua vậy đó

Vậy thì, nên lựa chọn giữa tốc độ (bay vào code ào ào) hay chất lượng (uống miếng nước ăn miếng bánh, viết miếng test case trước rồi mới code)? Bỏ thời gian ra ngồi debug và cãi nhau với tester hay nhận bug về chạy test để biết được vấn đề nằm ở đâu rồi fix một cách nhanh chóng? Trở thành một lập trình viên chuyên nghiệp hay một công nhân fix bug, fix vài năm rồi lên làm leader ? Hy vọng đến đây các bạn đã có câu trả lời cho chính bản thân mình.

Happy bug fixing ^^

Bình luận

Bài viết tương tự

- vừa được xem lúc

Bài toán tìm đường đi ngắn nhất với giải thuật Dijkstra

Với các bạn sinh viên chuyên ngành công nghệ thông tin, chắc không lạ gì với bài toán tìm đường đi ngắn nhất (Shortest Path Problems) trong đồ thị trọng số nữa. Ở bài viết lần này, mình sẽ làm 3 việc:.

0 0 135

- vừa được xem lúc

Tôi cá là bạn không biết những điều này - Ruby on rails ( Phần 2)

Các bạn có thể theo dõi phần 1 ở đây :. https://viblo.asia/p/toi-ca-la-ban-khong-biet-nhung-dieu-nay-ruby-on-rails-phan-1-WAyK8DDeKxX. 5.

0 0 222

- vừa được xem lúc

Những thay đổi trong ruby 3.0

. 2020 là một năm lớn đối với cộng đồng Ruby. Những người sáng lập Ruby có một món quà thực sự tuyệt vời cho chúng ta vào giáng sinh với việc phát hành Ruby 3.

0 0 47

- vừa được xem lúc

Có gì đặc biệt trong phiên bản Ruby 3x3 ?

Hello guys, chắc hẳn thời gian vừa rồi chúng ta cũng đã nghe qua thông tin Ruby sắp cho ra mắt Ruby version 3, hay còn được gọi là ruby 3x3, vậy liệu Ruby version 3 này có gì mới, và có những update nào đáng phải kể đến, và tại sao mọi người lại gọi nó là ruby version 3x3, thì trong bài ngày hôm nay

0 0 43

- vừa được xem lúc

Cách sử dụng class Time & Date trong Ruby (Phần 1)

Time là một class trong Ruby, nó sẽ giúp chỉnh sửa format, trích xuất thông tin một cách hiệu quả theo ý của bạn. . Topic hôm nay chúng ta có gì nào. .

0 0 97

- vừa được xem lúc

Ruby 3.0 có gì mới

Ruby 3.0.0 đã được ra mới được ra mắt vào tháng 12/2020, mục tiêu của bản 3.0.

0 0 41