07/09/2018, 17:08

Tìm hiểu về phương pháp lập trình Test Driven Development (part2)

Mình sẽ tiếp tục part 2 về TDD Các bạn theo dõi part 1 tại đây Ở part 1 mình đã viết test 1 cho nhận vào một số chia hết cho 3, trả về chuỗi Fizz, bắt tay vào test 2 nào 2. Nhận vào một số chia hết cho 5, trả về chuỗi Buzz Trường hợp thứ 2 là hàm run() nhận vào một số n chia hết cho 5 (nhưng ...

Mình sẽ tiếp tục part 2 về TDD
Các bạn theo dõi part 1 tại đây
Ở part 1 mình đã viết test 1 cho nhận vào một số chia hết cho 3, trả về chuỗi Fizz, bắt tay vào test 2 nào

2. Nhận vào một số chia hết cho 5, trả về chuỗi Buzz

Trường hợp thứ 2 là hàm run() nhận vào một số n chia hết cho 5 (nhưng nhớ là không chia hết cho 3 nhé), ví dụ 5, 10, 20,... và nó sẽ phải trả về kết quả là chuỗi "Buzz".

Viết test trước...

Vậy thì hàm test thứ 2 sẽ như sau:

fizzbuzz_test.rb

def test_fizzbuzz_run_return_buzz
  expect = "Buzz"
  actual = FizzBuzz.run(10)
  assert_equal expect, actual
end

Chạy thử luôn:

# Running:

F.

Finished in 0.001374s, 1455.6041 runs/s, 1455.6041 assertions/s.

  1) Failure:
FizzBuzzTest#test_fizzbuzz_run_return_buzz [/Users/tuan/Desktop/Ruby/rbfizzbuzz/fizzbuzz_test.rb:14]:
Expected: "Buzz"
  Actual: "Fizz"

2 runs, 2 assertions, 1 failures, 0 errors, 0 skips
rake aborted!

Output như trên có nghĩa là chạy 2 test case mà có 1 cái fail. Kết quả mong đợi của cái test fail là "Buzz" còn output thì lại là "Fizz". Vì chúng ta chưa implement trường hợp số chia hết cho 5, và cũng chưa phân biệt trường hợp chia hết cho 3 luôn, nên nó chỉ return về "Fizz".

...Viết code sau

Chúng ta implement trường hợp này như sau:

fizzbuzz.rb

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

Giờ chạy lại nào

# Running:

..

Finished in 0.001176s, 1700.6803 runs/s, 1700.6803 assertions/s.

2 runs, 2 assertions, 0 failures, 0 errors, 0 skips

Như các bạn thấy, thì vì test case thứ 2 đã phân biệt 2 trường hợp cụ thể, nên mình đã phải implement lệnh if...else để kiểm tra chia hết cho 3 hay 5 luôn rồi. Đi tiếp nào.

3.Nhận vào một số cùng chia hết cho 3 và 5, trả về chuỗi FizzBuzz

Trường hợp thứ 3 là chạy hàm run() với input là một số n cùng chia hết cho 3 và 5, ví dụ như là 15, 30, 45,...
Viết test trước... fizzbuzz_test.rb

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

Vậy là giờ chúng ta phải implement cho trường hợp cùng chia hết cho cả 3 và 5
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

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!

Fail??, xem lại code phải đưa trường hợp chia hết cho cả 3 và 5 lên trước, giải quyết ngay từ đầu
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

Run:

# Running:

...

Finished in 0.001329s, 2257.3363 runs/s, 2257.3363 assertions/s.

3 runs, 3 assertions, 0 failures, 0 errors, 0 skips

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

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

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

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

module FizzBuzz
  extend self

  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
end
require 'minitest/autorun'

require './FizzBuzz'


class FizzBuzzTest < Minitest::Test
  def test_fizzbuzz_run_return_fizz
    expect = "Fizz"
    actual = FizzBuzz.run(6)
    assert_equal expect, actual
  end

  def test_fizzbuzz_run_return_buzz
    expect = "Buzz"
    actual = FizzBuzz.run(10)
    assert_equal expect, actual
  end

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

  def test_fizzbuzz_run_return_n
    expect = 8
    actual = FizzBuzz.run(8)
    assert_equal expect, actual
  end
end
require "rake/testtask"


Rake::TestTask.new(:test) do |t|
  t.libs << "test"
  t.test_files = FileList["*_test.rb"]
  t.verbose = true
end

task :default => :test

Để á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 đó
Cám ơn các bạn đã theo dõi

0