[Tạp chí Lập trình] Unit Testing (Kiểm thử Đơn vị) là một kỹ thuật quan trọng góp phần lớn trong việc nâng cao chất lượng phần mềm. XP (Extreme Programming) coi kiểm thử đơn vị như là một trong những kỹ thuật cốt lõi. Hiện nay, nền công nghiệp phần mềm sẽ khó chấp nhận một lập trình viên không biết hoặc không thành thạo Kiểm thử Đơn vị. Các ngôn ngữ lập trình phổ biến hiện nay đều có sẵn những framework (khung làm việc) giúp lập trình viên triển khai dễ dàng Kiểm thử đơn vị. Thay vì bạn phải tự viết các chương trình độc lập để kiểm thử hoặc phải chờ những chức năng này được tích hợp rồi mới tiến hành kiểm thử. Khung làm việc Kiểm thử Đơn vị sẽ trợ giúp các lập trình viên nhanh chóng tạo ra những test-case (ca kiểm thử), và chạy các test-case để đảm bảo chức năng mà họ vừa xây dựng đạt chất lượng (không có bug, đúng yêu cầu nghiệp vụ, v.v.). Không những thế việc sử dụng các khung làm việc sẽ giúp họ dễ dàng triển khai Automation Testing (kiểm thử tự động) và tham gia vào các dự án phát triển phần mềm với sự trợ giúp của CI (Continuous Integration – Tích hợp Liên tục).
Tạp chí Lập trình đã có những bài viết giới thiệu với bạn đọc một số hướng dẫn sử dụng các khung làm việc để triển khai Kiểm thử Đơn vị:
● Kiểm thử đơn vị trên Android
● Kiểm thử Đơn vị trong JavaScript với QUnit
● Kiểm thử đơn vị với PHPUnit trên Netbeans
Trong bài viết này tôi sẽ hướng dẫn các bạn cách thức cài đặt và sử dụng NUnit, một khung làm việc được đánh giá cao dành cho ngôn ngữ C#. Khung làm việc này tích hợp với Visual Studio dưới dạng một Extension (với bài viết này tôi dùng VS 2010 để trình bày, bạn có thể làm tương tự với các bản VS khác).
Trước hết bạn cần kiểm tra xem bản VS mà bạn đang sử dụng đã tích hợp sẵn NUnit chưa? Để làm việc này trên thanh trình đơn của VS bạn chọn Tools > Extension Manager, trong phần Installed Extensions bạn chọn Tools, nếu kết quả như sau:
Nếu chưa có Extension này bạn có thể cài đặt thêm bằng cách chọn tiếp Online Gallery > Tools rồi tìm theo từ khóa “NUnit”, VS sẽ kết nối Internet và tìm kiếm cho bạn.
Bạn có thể tìm kiếm trực tiếp NUnit và tải về từ trang web: http://visualstudiogallery.msdn.microsoft.com. Bước tiếp theo bạn tiến hành tải, cài đặt Extension này và khởi động lại VS. Để chắc chắn VS đã tích hợp thành công NUnit bạn nên kiểm tra lại như tôi đã hướng dẫn ở trên.
Phần kế tiếp tôi sẽ hướng dẫn bạn cách sử dụng khung làm việc này cho một tình huống cụ thể. Giả sử bạn phải xây dựng một lớp có nhiệm vụ đọc một con số. Lớp này được thiết kế đơn giản như sau:
Phương thức SayNumber() của lớp Counter có nhiệm vụ đọc một số (được truyền vào bởi tham số number) theo quy tắc đơn giản sau:
- “Fizz” nếu number chia hết cho 3
- “Buzz” nếu number chia hết cho 5
- “FizzBuzz” nếu number chia hết cho cả 3 và 5
- Đúng số number (ở dạng chuỗi) nếu không phải các trường hợp trên
Bây giờ chúng ta bắt đầu giải quyết bài toán này. Trước tiên bạn cần tạo lớp Counter trong một project trên VS.
[sourcecode language=”csharp”]
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace FizzBuzz
{
public class Counter
{
public string SayNumber(int number) {
throw new NotImplementedException();
}
}
}
[/sourcecode]
Bạn chớ vội hoàn thành code của phương thức này. Chúng ta sẽ bắt đầu bằng việc tạo cho phương thức này một test-case bằng cách như sau:
VS sẽ sử dụng NUnit để tạo cho bạn một lớp có nhiệm vụ kiểm thử các phương thức của Counter, cụ thể là kiểm thử phương thức SayNumber().
Bạn cần tạo một project để chứa các lớp kiểm thử này. Kết quả bạn sẽ có lớp CounterTest như sau:
[sourcecode language=”csharp”]
using FizzBuzz;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
namespace TestFizzBuzz
{
…
[TestClass()]
public class CounterTest
{
…
/// <summary>
///A test for SayNumber
///</summary>
[TestMethod()]
public void SayNumberTest()
{
Counter target = new Counter(); // TODO: Initialize to an appropriate value
int number = 0; // TODO: Initialize to an appropriate value
string expected = string.Empty; // TODO: Initialize to an appropriate value
string actual;
actual = target.SayNumber(number);
Assert.AreEqual(expected, actual);
Assert.Inconclusive("Verify the correctness of this test method.");
}
}
}
[/sourcecode]
Bây giờ, bạn hãy tập trung vào phương thức SayNumberTest() của CounterTest, các vùng thông tin khác bạn sẽ dành thời gian tìm hiểu thêm sau khi đã thạo các bước căn bản.
Hiện tại NUnit đã tự động sinh ra trong phương thức này một loạt các mã lệnh để kiểm thử phương thức SayNumber(). Chúng ta sẽ điều chỉnh một chút code của phương thức này:
[sourcecode language=”csharp”]
public void SayNumber1Test()
{
Counter target = new Counter();
int number = 1;
string expected = "1";
string actual;
actual = target.SayNumber(number);
Assert.AreEqual(expected, actual);
}
[/sourcecode]
Những điều chỉnh với phương thức này:
- Đổi tên thành SayNumber1Test()
- Thay number với giá trị mới (số 1) – giá trị đầu vào
- Thay expected bằng chuỗi “1” – kết quả đầu ra cần đạt
- Bỏ dòng lệnh cuối cùng đi (Assert.Inconclusive(“Verify the correctness of this test method.”))
SayNumber1Test() sẽ thực thi một test-case đối với phương thức SayNumber(). Trường hợp đầu tiên chúng ta kiểm thử đó là cho đầu vào là số 1, và nếu phương thức thực hiện đúng thì kết quả nhận được sẽ phải là chuỗi “1”.
Bây giờ chúng ta sẽ chạy thử cái test-case này xem sao.
Kết quả nhận được là Failed:
Đây là dấu hiệu đáng mừng. Vì điều này cho thấy bạn đã hoàn thành hai pha đầu tiên trong TDD (Test-Driven Development – Phát triển Hướng Kiểm thử).
Giờ mới là lúc chúng ta quay lại để viết thêm một chút mã cho phương thức SayNumber(), bạn lưu ý rằng mình sẽ chỉ bổ sung một chút mã thôi, đủ để vượt qua cái test-case vừa rồi. Tôi sẽ code như sau:
[sourcecode language=”csharp”]
public string SayNumber(int number) {
return number + "";
}
[/sourcecode]
Chỉ đơn giản là một dòng lệnh return như trên. Sau đó chúng ta sẽ trở lại chạy cái test-case lúc trước. Và kết quả là…
Xanh rồi (Passed) nhé! Vậy là chúng ta có thể tự tin SayNumber() sẽ đảm bảo thành công với những trường hợp số bình thường (không phải 3 trường hợp đầu tiên theo yêu cầu của bài toán). Nếu chưa tin bạn có thể thay số 1 bằng các số 2, 4, 7, v.v..
Nếu theo thói quen thông thường bạn sẽ quay lại viết tiếp những mã lệnh cần thiết cho SayNumber(), nhưng đừng vội làm thế. Tại sao lại vậy? sao không hoàn chỉnh luôn phương thức SayNumber()? Thật ra bài toán mà chúng ta đang có ở đây chỉ là công cụ để ta triển khai Kiểm thử Đơn vị với phương pháp TDD mà thôi. Bạn sẽ quen dần với cách thức này và thấy sự thú vị và lợi ích mà TDD mang lại cho những lập trình viên. Nếu không thì tại sao các nghệ nhân phần mềm (software craftsman) lại khuyên ta nên thực hành TDD?!
Trở lại với các test-case trong lớp CounterTest, lúc này tôi sẽ viết một phương thức khác (SayNumber3Test) để thử một trường hợp mới mà tôi nghĩ chắc chắn SayNumber() không thể vượt qua được. Test-case đó như sau:
[sourcecode language=”csharp”]
[TestMethod()]
public void SayNumber3Test()
{
Counter target = new Counter();
int number = 3;
string expected = "Fizz";
string actual;
actual = target.SayNumber(number);
Assert.AreEqual(expected, actual);
}
[/sourcecode]
Chắc bạn đã quen với mấy dòng mã này, tôi đang thử với trường hợp số 3, theo đề bài thì SayNumber() phải cho tôi kết quả là “Fizz”. Vì tôi nghĩ là SayNumber() không làm được điều này và để khẳng định tôi lại “Run Test”. Kết quả đây…
Đúng như tôi mong đợi, test-case đầu tiên thành công nhưng test-case mới thì đỏ rồi, nguyên nhân thì chắc bạn đã rõ. Bây giờ chúng ta sẽ viết thêm một chút mã nữa cho SayNumber() để test-case này xanh nhé. Tôi làm thế này:
[sourcecode language=”csharp”]
public string SayNumber(int number)
{
if (number % 3 == 0)
{
return "Fizz";
}
return number + "";
}
[/sourcecode]
Chúng ta chạy lại các kiểm thử xem sao nhé.
Tuyệt vời, xanh hết rồi phải không bạn. Giờ thì tôi có thể khẳng định SayNumber() thành công với mọi trường hợp số chia hết cho 3. Mình lại làm một cái test-case nữa nhỉ?!
Thôi nhé! Tôi nghĩ đến như vậy đủ để bạn biết được cách triển khai Kiểm thử Đơn vị với NUnit. Bạn không nhất thiết phải triển khai Kiểm thử Đơn vị như tôi đã làm (nó lòng vòng quá phải không?). Về căn bản, bạn có thể viết mã hoàn chỉnh cho SayNumber() sau đó chuyển sang viết toàn bộ các test-case mà theo đó có thể đảm bảo phương thức SayNumber() đảm bảo chất lượng. Bước cuối cùng là “Run Test” để kiểm tra. Nhưng nếu là tôi, tôi sẽ vẫn làm theo cách thức ở trên. Và tôi sẽ thực hiện thêm một bước rất quan trọng nữa trong TDD. Đó là Code Refactor (Tái cấu trúc mã nguồn). Tôi sẽ tiến hành tái cấu trúc SayNumber() trước khi viết thêm một test-case, mặc dù nó đang khá ít mã lệnh và đang rất đơn giản. Bạn nghĩ mình sẽ tái cấu trúc nó thế nào đây, bạn thử xem sao nhé!
Chúc bạn sử dụng thành thạo NUnit và thấy được lợi ích đem lại của TDD trong phát triển phần mềm :o)
VS 2012 có nhiều điểm khác, do đó cần cài các Extension sau đây để có thể làm việc tốt với NUnit:
+ NUnit
+ NUnit Test Adapter
Và cài thêm “Unit Test Generator” do VS 2012 đã bỏ mất chức năng tạo Test trong Context Menu
http://visualstudiogallery.msdn.microsoft.com/45208924-e7b0-45df-8cff-165b505a38d7