Developer không chỉ code mà cần phải viết test đi kèm theo với Code
Trong ngôn ngữ Ruby, RSpec là một trong những testing framework được sử dụng phổ biến nhất. Việc viết test khi coding giúp ích rất nhiều cho developer trong việc đảm bảo chất lượng của các đoạn code được test, giúp điều tra bug xảy ra dễ dàng hơn.
Bài viết này chúng ta cùng tìm hiểu một số tip nên được sử dụng khi viết rspec, giúp các đoạn code testing trở nên hiệu quả và cũng dễ dàng trong việc đọc, maintain
Structure code to right place
Tất cả các test case đều bao gồm 3 block:
- Setup: Phần code chuẩn bị trước khi chạy test, như khởi tạo các biến , action(
let
,let!
), chạy các function trước khi test(before
) - Assert: Phần code chứa các test case được khai báo(
it
) - Teardown: Phần code sau khi đã chạy qua các test case(
after
), thường có nhiệm vụ dọn dẹp, khôi phục các trạng thái đượcsetup
ban đầu
Khi viết test ta nên cấu trúc, phân chia code theo đúng các block ở trên:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
<span class="token comment"># BAD</span> describe <span class="token string">"#upload"</span> <span class="token keyword">do</span> it <span class="token string">"should upload success"</span> <span class="token keyword">do</span> user<span class="token punctuation">.</span>uploader<span class="token punctuation">.</span>open expect <span class="token punctuation">{</span> user<span class="token punctuation">.</span>upload <span class="token punctuation">}</span><span class="token punctuation">.</span>to change <span class="token punctuation">{</span> user<span class="token punctuation">.</span>images<span class="token punctuation">.</span>size <span class="token punctuation">}</span><span class="token punctuation">.</span>by<span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span> user<span class="token punctuation">.</span>uploader<span class="token punctuation">.</span>close <span class="token keyword">end</span> <span class="token keyword">end</span> <span class="token comment"># GOOD</span> describe <span class="token string">"#upload"</span> <span class="token keyword">do</span> <span class="token comment"># Setup</span> let<span class="token punctuation">(</span><span class="token symbol">:user</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> create <span class="token symbol">:user</span><span class="token punctuation">,</span> <span class="token symbol">:with_images</span> <span class="token punctuation">}</span> let<span class="token punctuation">(</span><span class="token symbol">:action</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> user<span class="token punctuation">.</span>upload <span class="token punctuation">}</span> before <span class="token punctuation">{</span> user<span class="token punctuation">.</span>uploader<span class="token punctuation">.</span>open <span class="token punctuation">}</span> <span class="token comment"># Teardown</span> after <span class="token punctuation">{</span> user<span class="token punctuation">.</span>uploader<span class="token punctuation">.</span>close <span class="token punctuation">}</span> <span class="token comment"># Assert</span> it <span class="token string">"should upload success"</span> <span class="token keyword">do</span> expect <span class="token punctuation">{</span> action <span class="token punctuation">}</span><span class="token punctuation">.</span>to change <span class="token punctuation">{</span> user<span class="token punctuation">.</span>images<span class="token punctuation">.</span>size <span class="token punctuation">}</span><span class="token punctuation">.</span>by<span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span> <span class="token keyword">end</span> <span class="token keyword">end</span> |
Use instance_double instead of double
Trong quá trình viết test, có thể ta muốn mock
instances của một Class nào đó, khi đó sử dụng instance_double
sẽ an toàn hơn sử dụng double
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
<span class="token keyword">class</span> <span class="token class-name">User</span> <span class="token keyword">def</span> <span class="token method-definition"><span class="token function">show_info</span></span> <span class="token comment"># ...</span> <span class="token keyword">end</span> <span class="token keyword">end</span> <span class="token comment"># use double</span> sherlock <span class="token operator">=</span> double<span class="token punctuation">(</span><span class="token string">"User"</span><span class="token punctuation">)</span> allow<span class="token punctuation">(</span>sherlock<span class="token punctuation">)</span><span class="token punctuation">.</span>to receive<span class="token punctuation">(</span><span class="token symbol">:show_info</span><span class="token punctuation">)</span> <span class="token comment"># => OK</span> allow<span class="token punctuation">(</span>sherlock<span class="token punctuation">)</span><span class="token punctuation">.</span>to receive<span class="token punctuation">(</span><span class="token symbol">:show_info</span><span class="token punctuation">)</span><span class="token punctuation">.</span>with<span class="token punctuation">(</span><span class="token symbol">:name</span><span class="token punctuation">)</span> <span class="token comment"># => Fail, nhưng không raise exception</span> allow<span class="token punctuation">(</span>sherlock<span class="token punctuation">)</span><span class="token punctuation">.</span>to receive<span class="token punctuation">(</span><span class="token symbol">:some_method</span><span class="token punctuation">)</span> <span class="token comment"># Fail, cũng không raise exception</span> <span class="token comment"># use instance_double</span> sherlock <span class="token operator">=</span> instance_double<span class="token punctuation">(</span><span class="token string">"User"</span><span class="token punctuation">)</span> allow<span class="token punctuation">(</span>sherlock<span class="token punctuation">)</span><span class="token punctuation">.</span>to receive<span class="token punctuation">(</span><span class="token symbol">:show_info</span><span class="token punctuation">)</span> <span class="token comment"># => OK</span> allow<span class="token punctuation">(</span>sherlock<span class="token punctuation">)</span><span class="token punctuation">.</span>to receive<span class="token punctuation">(</span><span class="token symbol">:show_info</span><span class="token punctuation">)</span><span class="token punctuation">.</span>with<span class="token punctuation">(</span><span class="token symbol">:name</span><span class="token punctuation">)</span> <span class="token comment"># => Fail, raise wrong number of argument</span> allow<span class="token punctuation">(</span>sherlock<span class="token punctuation">)</span><span class="token punctuation">.</span>to receive<span class="token punctuation">(</span><span class="token symbol">:some_method</span><span class="token punctuation">)</span> <span class="token comment"># Fail, raise method not implement</span> |
DESCRIBE for testing target, CONTEXT for scenarios
Dùng Describe để chỉ định đối tượng cần test, Context để mô tả các test case có thể xảy ra, giúp việc code trở nên dễ track hơn, hơn nữa, các context
cũng được phân chia, phân biệt với các context
khác
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
describe <span class="token string">"#authenticate"</span> <span class="token keyword">do</span> let<span class="token operator">!</span><span class="token punctuation">(</span><span class="token symbol">:user</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> create <span class="token symbol">:user</span><span class="token punctuation">,</span> email<span class="token punctuation">:</span> <span class="token string">"<a class="__cf_email__" href="/cdn-cgi/l/email-protection">[email protected]</a>"</span><span class="token punctuation">,</span> password<span class="token punctuation">:</span> <span class="token string">"123456"</span> <span class="token punctuation">}</span> context <span class="token string">"when email and password not match"</span> <span class="token keyword">do</span> let<span class="token punctuation">(</span><span class="token symbol">:param</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token punctuation">{</span> email<span class="token punctuation">:</span> <span class="token string">"<a class="__cf_email__" href="/cdn-cgi/l/email-protection">[email protected]</a>"</span><span class="token punctuation">,</span> password<span class="token punctuation">:</span> <span class="token string">"xxxxxx"</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> it <span class="token string">"should return failure"</span> <span class="token keyword">do</span> <span class="token comment"># ...</span> <span class="token keyword">end</span> <span class="token keyword">end</span> context <span class="token string">"when email and password match"</span> <span class="token keyword">do</span> let<span class="token punctuation">(</span><span class="token symbol">:param</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token punctuation">{</span> email<span class="token punctuation">:</span> <span class="token string">"<a class="__cf_email__" href="/cdn-cgi/l/email-protection">[email protected]</a>"</span><span class="token punctuation">,</span> password<span class="token punctuation">:</span> <span class="token string">"123456"</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> it <span class="token string">"should return success"</span> <span class="token keyword">do</span> <span class="token comment"># ...</span> <span class="token keyword">end</span> <span class="token keyword">end</span> <span class="token keyword">end</span> |
Understand how transactions work in RSpec
Transaction
mặc định được tạo vào gắn mỗi một example
trong Rspec
, cho phép database
rolled backed
về trạng thái ban đầu, sử dụng cho những example
sau đó.
Các record được tạo trong before(:context)
hay before(:all)
sẽ không bị rolled back
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
context <span class="token string">"context 1"</span> <span class="token keyword">do</span> before<span class="token punctuation">(</span><span class="token symbol">:context</span><span class="token punctuation">)</span> <span class="token keyword">do</span> create<span class="token punctuation">(</span><span class="token symbol">:user</span><span class="token punctuation">)</span> <span class="token comment"># WON"T BE ROLLED-BACK</span> <span class="token keyword">end</span> before <span class="token keyword">do</span> create<span class="token punctuation">(</span><span class="token symbol">:user</span><span class="token punctuation">)</span> <span class="token comment"># will be rolled-back</span> <span class="token keyword">end</span> <span class="token comment"># ...</span> <span class="token keyword">end</span> context <span class="token string">"context 2"</span> <span class="token keyword">do</span> before<span class="token punctuation">(</span><span class="token symbol">:context</span><span class="token punctuation">)</span> <span class="token keyword">do</span> create<span class="token punctuation">(</span><span class="token symbol">:user</span><span class="token punctuation">)</span> <span class="token comment"># WON"T BE ROLLED-BACK</span> <span class="token keyword">end</span> <span class="token comment"># ...</span> <span class="token keyword">end</span> <span class="token comment"># BY NOW, THERE ARE 2 USER RECORDS COMMITED TO DATABASE</span> |
Use bulk method if possible
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
<span class="token comment"># BAD</span> it <span class="token string">"should update user success"</span> <span class="token keyword">do</span> expect<span class="token punctuation">(</span>user<span class="token punctuation">.</span>name<span class="token punctuation">)</span><span class="token punctuation">.</span>eq <span class="token string">"Sherlock"</span> expect<span class="token punctuation">(</span>user<span class="token punctuation">.</span>email<span class="token punctuation">)</span><span class="token punctuation">.</span>eq <span class="token string">"<a class="__cf_email__" href="/cdn-cgi/l/email-protection">[email protected]</a>"</span> expect<span class="token punctuation">(</span>user<span class="token punctuation">.</span>sex<span class="token punctuation">)</span><span class="token punctuation">.</span>eq <span class="token string">"Male"</span> expect<span class="token punctuation">(</span>user<span class="token punctuation">.</span>address<span class="token punctuation">)</span><span class="token punctuation">.</span>eq <span class="token string">"221 Baker street"</span> expect<span class="token punctuation">(</span>user<span class="token punctuation">.</span>phone<span class="token punctuation">)</span><span class="token punctuation">.</span>eq <span class="token string">"12345678"</span> <span class="token keyword">end</span> <span class="token comment"># GOOD</span> it <span class="token string">"should update user success"</span> <span class="token keyword">do</span> expect<span class="token punctuation">(</span>user<span class="token punctuation">)</span><span class="token punctuation">.</span>to have_attributes<span class="token punctuation">(</span> name<span class="token punctuation">:</span> <span class="token string">"Sherlock"</span><span class="token punctuation">,</span> email<span class="token punctuation">:</span> <span class="token string">"<a class="__cf_email__" href="/cdn-cgi/l/email-protection">[email protected]</a>"</span><span class="token punctuation">,</span> sex<span class="token punctuation">:</span> <span class="token string">"Male"</span><span class="token punctuation">,</span> address<span class="token punctuation">:</span> <span class="token string">"221 Baker street"</span><span class="token punctuation">,</span> phone<span class="token punctuation">:</span> <span class="token string">"12345678"</span> <span class="token punctuation">)</span> <span class="token keyword">end</span> |
VIết như trên tránh việc lặp những đoạn code giống nhau (DRY), cũng như chỉ ra tất cả các assertion
đều dùng cho một object
Use configs for patterned test case
Cũng là một các viết DRY, code trông gọn hơn và giúp cho người đọc dễ follow hơn:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
<span class="token comment"># BAD</span> <span class="token comment">#</span> describe <span class="token string">".extract_extension"</span> <span class="token keyword">do</span> subject <span class="token punctuation">{</span> described_class<span class="token punctuation">.</span>extract_extension<span class="token punctuation">(</span>filename<span class="token punctuation">)</span> <span class="token punctuation">}</span> context <span class="token string">"when the filename is empty"</span> <span class="token keyword">do</span> let<span class="token punctuation">(</span><span class="token symbol">:filename</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token string">""</span> <span class="token punctuation">}</span> it <span class="token punctuation">{</span> is_expected<span class="token punctuation">.</span>to eq <span class="token string">""</span> <span class="token punctuation">}</span> <span class="token keyword">end</span> context <span class="token string">"when the filename is video123.mp4"</span> <span class="token keyword">do</span> let<span class="token punctuation">(</span><span class="token symbol">:filename</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token string">"video123.mp4"</span> <span class="token punctuation">}</span> it <span class="token punctuation">{</span> is_expected<span class="token punctuation">.</span>to eq <span class="token string">"mp4"</span> <span class="token punctuation">}</span> <span class="token keyword">end</span> context <span class="token string">"when the filename is video.edited.mp4"</span> <span class="token keyword">do</span> let<span class="token punctuation">(</span><span class="token symbol">:filename</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token string">"video.edited.mp4"</span> <span class="token punctuation">}</span> it <span class="token punctuation">{</span> is_expected<span class="token punctuation">.</span>to eq <span class="token string">"mp4"</span> <span class="token punctuation">}</span> <span class="token keyword">end</span> context <span class="token string">"when the filename is video-edited"</span> <span class="token keyword">do</span> let<span class="token punctuation">(</span><span class="token symbol">:filename</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token string">"video-edited"</span> <span class="token punctuation">}</span> it <span class="token punctuation">{</span> is_expected<span class="token punctuation">.</span>to eq <span class="token string">""</span> <span class="token punctuation">}</span> <span class="token keyword">end</span> context <span class="token string">"when the filename is .mp4"</span> <span class="token keyword">do</span> let<span class="token punctuation">(</span><span class="token symbol">:filename</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token string">".mp4"</span> <span class="token punctuation">}</span> it <span class="token punctuation">{</span> is_expected<span class="token punctuation">.</span>to eq <span class="token string">""</span> <span class="token punctuation">}</span> <span class="token keyword">end</span> <span class="token keyword">end</span> <span class="token comment"># GOOD</span> <span class="token comment">#</span> describe <span class="token string">".extract_extension"</span> <span class="token keyword">do</span> subject <span class="token punctuation">{</span> described_class<span class="token punctuation">.</span>extract_extension<span class="token punctuation">(</span>filename<span class="token punctuation">)</span> <span class="token punctuation">}</span> test_cases <span class="token operator">=</span> <span class="token punctuation">[</span> <span class="token string">""</span> <span class="token operator">=</span><span class="token operator">></span> <span class="token string">""</span><span class="token punctuation">,</span> <span class="token string">"video123.mp4"</span> <span class="token operator">=</span><span class="token operator">></span> <span class="token string">"mp4"</span> <span class="token string">"video.edited.mp4"</span> <span class="token operator">=</span><span class="token operator">></span> <span class="token string">"mp4"</span> <span class="token string">"video-edited"</span> <span class="token operator">=</span><span class="token operator">></span> <span class="token string">""</span> <span class="token string">".mp4"</span> <span class="token operator">=</span><span class="token operator">></span> <span class="token string">""</span> <span class="token punctuation">]</span> test_cases<span class="token punctuation">.</span><span class="token keyword">each</span> <span class="token keyword">do</span> <span class="token operator">|</span>test_filename<span class="token punctuation">,</span> extension<span class="token operator">|</span> context <span class="token string">"when filename = <span class="token interpolation"><span class="token delimiter tag">#{</span>test_filename<span class="token delimiter tag">}</span></span>"</span> <span class="token keyword">do</span> let<span class="token punctuation">(</span><span class="token symbol">:filename</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> test_filename <span class="token punctuation">}</span> it <span class="token punctuation">{</span> is_expected<span class="token punctuation">.</span>to eq extension <span class="token punctuation">}</span> <span class="token keyword">end</span> <span class="token keyword">end</span> <span class="token keyword">end</span> |
Trên đây là một số tip mình tìm hiểu được trong quá trình viết Rspec
, cảm ơn mọi người đã theo dõi