Unit Test với QUnit: Javascript

Unit test là công việc quen thuộc của các lập trình viên để kiểm tra một đơn vị mã nguồn có hoạt động chính xác hay không. Nếu bạn là một javascript coder và đang tìm kiếm một phương pháp thực hiện unit test, hãy thử sử dụng QUnit – một framework được tạo jQuery team và được sử dụng cho dự án jQuery.

Trang chủ và tài liệu: http://docs.jquery.com/Qunit
Mã nguồn dự án: https://github.com/jquery/qunit

 Chuẩn bị

Để sử dụng, bạn chỉ cần download hai tập tin sau: qunit.jsqunit.js

Và tạo một file HTML đơn giản có dạng như sau:

<html>
<head>
	<title>QUnit Test Example</title>

	<link rel="stylesheet" href="http://code.jquery.com/qunit/qunit-git.css" />
	<script src="http://code.jquery.com/qunit/qunit-git.js"></script>
</head>
<body>
	<div id="qunit"></div>

	<script>
	(function(){

		// your sourcecode goes here

	})();
	</script>
</body>
</html>

File HTML trên tôi tạo một hàm vô danh (anonymous function) tự động thực thi để chứa các đoạn mã sẽ viết. Phương pháp này được áp dụng phổ biến trong lập trình javascript vì giúp tránh việc tạo ra các định danh không cần thiết, và do đó hạn chế được các trường hợp xung đột (conflict) định danh.
Đừng quên thêm vào thẻ div có id là “qunit”, đây sẽ là nơi để QUnit hiển thị các thông tin và kết quả test lên trên trình duyệt. Nếu bạn mở file này bên trong trình duyệt, bạn sẽ thấy giao diện như sau:

QUnit Test Example - 1

Các tùy chọn

Giao diện QUnit tạo ra có 3 checkbox tùy chọn. Công dụng của chúng là:
Hide passed tests: ẩn các test chạy đúng. Việc sử dụng tùy chọn có thể khiến bạn khó xác định được test nào fail.
Check for Globals: test sẽ là fail nếu như một biến global (hay một thuộc tính của đối tượng window) được tạo ra khi thực thi. Việc này nhằm phát hiện và hạn chế việc tạo ra các biến global một cách không cần thiết và có thể gây ảnh hưởng đến toàn bộ trang web.
No try-catch: Chạy các test mà không sử dụng khối try catch. Tùy chọn này giúp bạn có thể phát hiện các vị trí lỗi và debug (thường kết hợp với tool như Firebug).

Tạo các Unit Test

Trước tiên, tôi tạo một test unit, ở đây là một function add(x, y) đơn giản có chức năng trả về tổng của hai tham số. Sau đó sử dụng hàm test() của QUnit để bắt đầu một unit test mới với hai tham số là tên và đoạn code chứa các mã test:

test( name, expected, test )

Ở đây tôi sử dụng assertion là equal() để so sánh hai giá trị bằng nhau:

equal( actual, expected, message )

(function(){
	// test unit
	function add(x, y){
		return x + y;
	}

	test('add()', function() {
		equal(add(1, 2), 3, "Passed: 1 + 2 = 3");
		equal(add(2, null), 3);
		equal(add_numbers(2, 2), 3);

	});

})();

Kết quả:

 

QUnit Test Example 2

Bạn thấy kết quả hiển thị có thông báo “1 tests of 3 passed, 2 failed“. Bên dưới liệt kê các test có đánh số thứ tự cùng với kết quả của chúng:

1. Passed: 1 + 2 = 3
2. failed (Do giá trị Expected và Result khác nhau)
3. Died (add_numbers không được định nghĩa)

Một test có trạng thái “died” tức là mọi test bên dưới nó sẽ không được thực hiện. Điều này xảy ra khi một lỗi nghiêm trọng có thể ảnh hưởng đến toàn bộ test bên dưới. Bạn cũng có thể tạo ra các test dạng này bằng cách dùng assertion ok():

ok( state, message )

Ví dụ các test unit của bạn cần đối tượng jQuery để chạy, vậy bạn có thể viết như sau:

test('ok() assertion', function() {
	ok(jQuery);

	// assertion calls

});

Test bất đồng bộ (Asynchronous)

Phương pháp test thông thường sẽ chạy lần lượt từ trên xuống dưới các lời gọi assertion (test đồng bộ – Synchronous). Trong trường hợp muốn test một cách bất đồng bộ, bạn có thể sử dụng hai hàm stop() và start() kết hợp với setTimeout()/setInterval().
start( decrement ) : Chạy lại test khi nó bị dừng bởi stop().
stop( increment ) : Dừng test cho đến khi start() được gọi.
Ví dụ:

test('asynchronous test', function() {
	stop();

	equal(1,1);
	setTimeout(start,1000);

});

Sẽ có kết quả là:

Tests completed in 1045 milliseconds.
1 tests of 1 passed, 0 failed.

Như bạn thấy, chỉ một lời gọi assertion đơn giản nhưng lại chiếm hơn 1000 milisecond để thực thi. Đó là do dòng lệnh equal(1,1) chỉ được thực hiện sau khi hàm start() được gọi.