Đo độ bao phủ code theo branch và method trong Ruby 2.5
Bài đăng này đã không được cập nhật trong 5 năm
Trong Ruby chúng ta có 1 thư viện chuẩn với tên gọi Coverage để đo độ bao phủ code trong quá trình chương trình được thực thi. Bài viết này sẽ giới thiệu với các bạn các loại độ bao phủ code và cách đo chúng bằng thư viện Corverage.
Trước Ruby 2.5
Line coverage
Trước Ruby 2.5 thì khi dùng Corverage chúng ta chỉ có thể đo độ bao phủ code theo dòng (line coverage). Độ bao phủ code theo dòng cho chúng ta biết liệu 1 dòng code có được thực thi hay không, và nếu được thực thi thì nó được thực thi bao nhiêu lần.
Giả sử chúng ta có 1file score.rb
với nội dung như dưới đây:
score = 33
if score >= 40
p :PASSED
else
p :FAILED
end
Chúng ta tạo 1 file nữa với tên gọi score_coverage.rb
.
require "coverage"
Coverage.start
load "score.rb"
p Coverage.result
Chúng ta sử dụng method Coverage#start
để đo độ bao phủ code trong file score.rb
còn method Coverage#result
trả về kết quả đo.
Thử chạy file score_coverage.rb
với Ruby 2.4
$ RBENV_VERSION=2.4.0 ruby score_coverage.rb
:FAILED
{ "score.rb"=> [1, nil, 1, 0, nil, 1, nil] }
Mỗi giá trị trong mảng [1, nil, 1, 0, nil, 1, nil]
cho biết số lần từng dòng code trong file score.rb
được thực thi. Mảng này cũng được gọi là line coverage của file score.rb
.
Giá trị nil
trong mảng line coverage có nghĩa là việc đo độ bao phủ không được thực hiện ở dòng đó hoặc dòng đó không phải là 1 dòng thích hợp. Đối với những dòng như else
, end
hoặc các dòng trống thì việc đo độ bao phủ sẽ không được thực hiện.
Dưới đây là cách đọc hiểu kết quả line coverage ở trên:
- Dòng 1 (ứng với index số 0 trong mảng) được thực thi 1 lần
- Việc đo độ bao phủ không được thực hiện ở dòng 2 vì dòng đó trống
- Dòng 3 được thực thi 1 lần
- Dòng 4 không được thực thi
- Việc đo độ bao phủ không được thực hiện ở dòng 5 vì dòng đó chỉ chứa mỗi từ khoá
else
- Dòng 6 được thực thi 1 lần
- Việc đo độ bao phủ không được thực hiện ở dòng 7 vì dòng đó chỉ chứa mỗi từ khoá
end
.
Sau Ruby 2.5
Kể từ Ruby 2.5 thì thư viện Coverage
đã được thêm 2 tính năng mới là đo độ bao phủ theo nhánh (branch coverage) và theo method (method coverage). Hãy cùng tìm hiểu những điểm thay đổi của thư viện Coverage
ở dưới đây.
Line coverage
Nếu chạy file score_coverage.rb
ở trên với Ruby 2.5 chúng ta sẽ thấy kết quả không có gì thay đổi.
$ RBENV_VERSION=2.5.0 ruby score_coverage.rb
:FAILED
{ "score.rb" => [1, nil, 1, 0, nil, 1, nil] }
Việc này là để đảm bảo method Coverage#start
có độ tương thích ngược 100%.
Nếu chúng ta bật option lines
khi gọi Coverage#start
thì kết quả sẽ thay đổi như sau
require "coverage"
Coverage.start(lines: true)
load "score.rb"
p Coverage.result
$ RBENV_VERSION=2.5.0 ruby score_coverage.rb
:FAILED
{ "score.rb" => {
:lines => [1, nil, 1, 0, nil, 1, nil]
}
}
Chúng ta có thể thấy kết quả đo độ bao phủ code của file score.rb
bây giờ là 1 hash và line coverage của file đó vẫn là [1, nil, 1, 0, nil, 1, nil]
.
Branch coverage
Branch coverage cho chúng ta biết những nhánh nào trong code được thực thi và những nhánh nào không được thực thi.
Bây giờ chúng ta sẽ sửa lại file score_coverage.rb
bằng việc bật option branches
require "coverage"
Coverage.start(branches: true)
load "score.rb"
p Coverage.result
$ RBENV_VERSION=2.5.0 ruby score_coverage.rb
:FAILED
{ "score.rb" =>
{ :branches => {
[:if, 0, 3, 0, 7, 3] => {
[:then, 1, 4, 2, 4, 15] => 0,
[:else, 2, 6, 2, 6, 15] => 1
}
}
}
}
Thông tin chứa trong những mảng ở trên bao gồm:
[
BRANCH_TYPE,
UNIQUE_ID,
START_LINE_NUMBER,
START_COLUMN_NUMBER,
END_LINE_NUMBER,
END_COLUMN_NUMBER
]
Chú ý là số thứ tự dòng được tính từ 1 còn số thứ tự cột được tính từ 0.
Nói 1 cách cụ thể thì
[:if, 0, 3, 0, 7, 3]
có nghĩa là câu lệnhif
bắt đầu ở dòng 3, cột 0 và kết thúc ở dòng 7, cột 3.[:then, 1, 4, 2, 4, 15]
có nghĩa là mệnh đềthen
bắt đầu ở dòng 4, cột 2 và kết thúc ở dòng 4, cột 15.[:else, 2, 6, 2, 6, 15]
có nghĩa là mệnh đềelse
bắt đầu ở dòng 6, cột 2 và kết thúc ở dòng 6, cột 15.
Ngoài những mảng này ra, từ kết quả ở trên chúng ta có thể thấy những thông tin quan trọng hơn là nhánh từ if
đến then
đã không được thực thi (giá trị ứng với key [:then, 1, 4, 2, 4, 15]
là 0) còn nhánh từ if
đến else
được thực thi 1 lần (giá trị ứng với key [:else, 2, 6, 2, 6, 15]
là 1).
Method coverage
Đo độ bao phủ theo method cho chúng ta biết những method nào được gọi và những method nào không được gọi.
Giả sử chúng ta có 1 file grade_calculator.rb
với nội dung như dưới đây
students_scores = { "Sam" => [53, 91, 72],
"Anna" => [91, 97, 95],
"Bob" => [33, 69, 63] }
def average(scores)
scores.reduce(&:+)/scores.size
end
def grade(average_score)
case average_score
when 90.0..100.0 then :A
when 80.0..90.0 then :B
when 70.0..80.0 then :C
when 60.0..70.0 then :D
else :F
end
end
def greet
puts "Congratulations!"
end
def warn
puts "Try hard next time!"
end
students_scores.each do |student_name, scores|
achieved_grade = grade(average(scores))
puts "#{student_name}, you've got '#{achieved_grade}' grade."
if achieved_grade == :A
greet
elsif achieved_grade == :F
warn
end
puts
end
Để đo độ bao phủ theo method của file trên, chúng ta sẽ tạo 1 file grade_calculator_coverage.rb
và sử dụng method Converage#start
với option methods
require "coverage"
Coverage.start(methods: true)
load "grade_calculator.rb"
p Coverage.result
Thử chạy file này với Ruby 2.5
$ RBENV_VERSION=2.5.0 ruby grade_calculator_coverage.rb
Sam, you've got 'C' grade.
Anna, you've got 'A' grade.
Congratulations!
Bob, you've got 'F' grade.
Try hard next time!
{ "grade_calculator.rb" => {
:methods => {
[Object, :warn, 23, 0, 25, 3] => 1,
[Object, :greet, 19, 0, 21, 3] => 1,
[Object, :grade, 9, 0, 17, 3] => 3,
[Object, :average, 5, 0, 7, 3] => 3
}
}
}
Những thông tin chứa trong các mảng ở trên bao gồm
[
CLASS_NAME,
METHOD_NAME,
START_LINE_NUMBER,
START_COLUMN_NUMBER,
END_LINE_NUMBER,
END_COLUMN_NUMBER
]
Từ đó chúng ta có thể thấy [Object, :grade, 9, 0, 17, 3] => 3
có nghĩa là method Object#grade
bắt đầu từ dòng 9, cột 0, kết thúc ở dòng 17, cột 3 và đã được gọi 3 lần trong quá trình thực thi file grade_calculator.rb
.
Một lưu ý cuối cùng là chúng ta có thể đo 3 loại độ bao phủ ở trên cùng 1 lúc bằng cách gọi
Coverage.start(lines: true, branches: true, methods: true)
Bài viết đến đây là kết thúc. Hi vọng nó sẽ có ích cho các bạn trong quá trình kiểm thử chương trình Ruby nói chung và Rails web app nói riêng. Cảm ơn các bạn đã theo dõi!
Tham khảo
Ruby 2.5 supports measuring branch and method coverages
https://blog.bigbinary.com/2018/04/11/ruby-2-5-supports-measuring-branch-and-method-coverages.html
All rights reserved