Khi số lượng script tự động tăng lên, việc tổ chức và bảo quản các test case cùng dữ liệu kiểm thử, tái sử dụng các module chung, lựa chọn và điều khiển thực thi test case, hoặc hợp tác nhóm và chuẩn hóa viết test case, chúng ta cần giới thiệu khung kiểm thử tự động. Khung là kiến trúc tổ chức ứng dụng, thường bao gồm tổ chức mã, cấu hình, dữ liệu, nhật ký, phụ thuộc, trích xuất module có thể tái sử dụng và điều khiển chạy. Giống như từ một nhóm lính rải rác, trở thành một đội quân. Khung là lựa chọn tất yếu khi các tập script phát triển thành ứng dụng (bao gồm dự án kiểm thử). Các chức năng cơ bản của khung thường bao gồm:
- Phân loại và tổ chức mã, tệp cấu hình, tệp dữ liệu, v.v.
- Quản lý phụ thuộc.
- Tái sử dụng module chung.
- Quy trình và điều khiển chạy.
Ngoài ra, về mục tiêu thiết kế, khung nên dễ sử dụng, mạnh mẽ và ổn định, hiệu năng tốt, cung cấp cách sử dụng linh hoạt thông qua cấu hình hoặc tham số, cũng như dễ gỡ lỗi và bảo trì (như cung cấp nhật ký chạy).
Khung kiểm thử là khung được thiết kế cho mục đích kiểm thử. Khung kiểm thử thường còn bao gồm viết test case, các phương thức assertion phong phú, tổ chức test case, phương pháp chuẩn bị và dọn dẹp kiểm thử, thực thi kiểm thử, tạo báo cáo kiểm thử, v.v. So với một tập hợp các script, khung kiểm thử thường có những ưu điểm sau:
- Thực thi test case độc lập, một test case thất bại không ảnh hưởng đến việc thực thi và xác minh các test case khác.
- Trạng thái thực thi test case rõ ràng, như thành công, thất bại, lỗi, bỏ qua, v.v. và báo cáo kiểm thử hệ thống.
- Lựa chọn test case linh hoạt và chạy hàng loạt.
- Cung cấp nhiều phương thức assertion (so sánh kết quả mong đợi với kết quả thực tế).
- Cung cấp phương pháp chuẩn bị và dọn dẹp kiểm thử ở nhiều phạm vi khác nhau.
- Bỏ qua test case khi điều kiện môi trường cụ thể không được đáp ứng.
Trong Python, các khung kiểm thử phổ biến bao gồm Unittest, Nose, Pytest và Robot Framework. Trong đó Unittest là khung kiểm thử đi kèm với Python, cung cấp các chức năng quản lý test case và điều khiển kiểm thử cơ bản, sử dụng linh hoạt và dễ tùy chỉnh. Chương này sử dụng unittest và Selenium để giải thích quy trình xây dựng khung kiểm thử tự động Web. Pytest và Robot Framework sẽ được trình bày ở chương khác.
9.1 Sử dụng cơ bản Unittest
Unitest là khung kiểm thử đơn vị đi kèm với Python, được viết theo JUnit, có phong cách tương tự như các khung kiểm thử đơn vị phổ biến của ngôn ngữ khác. Unittest chủ yếu bao gồm các khái niệm chính như TestCase test case, TestSuite test suite, TestFixture chuẩn bị và dọn dẹp kiểm thử, TestRunner trình chạy kiểm thử, ngoài ra còn bao gồm TestLoader để tải hàng loạt test case tạo test suite, TestResult để ghi lại kết quả kiểm thử trong TestRunner. Quan hệ mô hình và quy trình hoạt động của Unittest như hình 9.1所示.
Hình 9.1 Quan hệ mô hình và quy trình hoạt động của Unittest
Khi viết test case, cần viết một lớp (thường quy ước bắt đầu bằng Test) và kế thừa unittest.TestCase. Mỗi lớp kiểm thử chứa các phương thức kiểm thử (test case) và phương pháp TestFixture chuẩn bị và dọn dẹp kiểm thử. Các phương thức kiểm thử thường quy ước có tên bắt đầu bằng test (các phương thức trong lớp kiểm thử không bắt đầu bằng test không được thực thi như test case, thường có thể dùng làm bước phụ, v.v.). Mỗi phương thức kiểm thử khi thực thi sẽ được thực thể hóa thành một đối tượng TestCase để chạy. Đồng thời, lớp cha unittest.TestCase cung cấp nhiều phương thức assertion (so sánh kiểm thử) phong phú để xác minh kết quả.
TestFixture còn được gọi là giàn giáo kiểm thử hoặc thiết bị kiểm thử. Bao gồm hai loại phương pháp setup chuẩn bị kiểm thử và teardown dọn dẹp kiểm thử, được thực thi trước và sau khi thực thi test case, giống như hai lát bánh sandwich kẹp test case chính của chúng ta. TestFixture cung cấp chuẩn bị và dọn dẹp kiểm thử ở nhiều phạm vi khác nhau, bao gồm cấp test case, cấp lớp kiểm thử và cấp module kiểm thử.
TestSuite test suite được dùng để lựa chọn và tổ chức test case. Ngoài việc sử dụng phương thức addTest của test suite để thêm test case, còn có thể sử dụng đối tượng TestLoader để thêm hàng loạt một module kiểm thử, một lớp kiểm thử, v.v. để nhanh chóng tạo một đối tượng test suite. Đồng thời test suite hỗ trợ lồng nhau, khi thực thi sẽ thực thi theo thứ tự thêm test case và con suite, thực thi duyệt chiều sâu.
TestRunner được dùng để điều khiển chạy và xử lý ngoại lệ của test suite, các test case trong đó và các cấp độ TestFixture. TestRunner chứa một đối tượng TestResult, di chuyển qua các test case và suite để ghi lại và xuất kết quả theo thời gian thực.
Viết test case
Viết test case Unittest thường nên tuân theo các bước sau. (1) Tập lệnh kiểm thử (module kiểm thử) quy ước bắt đầu bằng test. (2) Lớp kiểm thử quy ước bắt đầu bằng Test và kế thừa unittest.TestCase. (3) Phương thức kiểm thử (test case) quy ước bắt đầu bằng test, có thể chứa nhiều bước và nhiều assertion.
Lưu ý: Trong lớp kiểm thử không nên ghi đè phương thức init của lớp cha, nếu không sẽ phá vỡ quá trình Unittest tạo đối tượng test case từ mỗi phương thức kiểm thử.
Lớp kiểm thử TestCase
Ví dụ viết lớp kiểm thử như sau.
import unittest
class KiemThuDemo(unittest.TestCase):
"""Ví dụ lớp kiểm thử"""
def cong(self, a, b): # Không bắt đầu bằng test không được coi là test case
return a + b
def test_cong_nguyen(self):
"""Kiểm thử cộng số nguyên""" # Sử dụng docstring để mô tả test case
ket_qua = self.cong(1, 2)
self.assertEqual(ket_qua, 3) # Kết quả thực tế, kết quả kỳ vọng
def test_cong_thuc(self):
"""Kiểm thử số thực"""
ket_qua = self.cong(10000.1, 0.1)
self.assertEqual(ket_qua, 10000.2)
def test_cong_chuoi(self):
"""Kiểm thử chuỗi"""
ket_qua = self.cong('10', '1')
self.assertEqual(ket_qua, '11')
if __name__ == '__main__':
unittest.main()
Trong lớp kiểm thử trên, phương thức cong không bắt đầu bằng test, không được chạy như test case, các phương thức không kiểm thử trong lớp kiểm thử có thể được dùng làm bước phụ cho các phương thức kiểm thử gọi.
Phương thức kiểm thử có thể sử dụng docstring (cặp ba dấu ngoặc kép kép chứa chuỗi), để cung cấp mô tả test case. Mô tả test case sẽ được hiển thị ở chế độ chi tiết.
self.assertEqual(ket_qua,3) là một phương thức assertion được lớp cha unittest.TestCase cung cấp, dùng để khẳng định bằng nhau. Trong test case cũng có thể sử dụng câu lệnh assert của Python trực tiếp, như assert ket_qua==3. Nhưng so sánh, việc sử dụng phương thức assertion được lớp TestCase cung cấp, trong thông báo thất bại có thể xem được kết quả đối chiếu rõ ràng hơn.
if __name__ == '__main__' có nghĩa là nếu module này được thực thi độc lập (không được gọi bởi module khác), unittest.main() cung cấp giao diện dòng lệnh cho tập lệnh hiện tại (hỗ trợ nhiều tham số chạy), khi chạy tập lệnh mặc định chạy tất cả các test case có thể tìm thấy trong module kiểm thử hiện tại theo quy tắc quy ước (lớp kiểm thử bắt đầu bằng Test, phương thức kiểm thử bắt đầu bằng test).
Chạy tập lệnh, kết quả chạy như sau.
..F
======================================================================
FAIL: test_cong_chuoi (__main__.KiemThuDemo)
Kiểm thử chuỗi
----------------------------------------------------------------------
Traceback (most recent call last):
File "/Users/superhin/Desktop/test_demo.py", line 22, in test_cong_chuoi
self.assertEqual(ket_qua, '11')
AssertionError: '101' != '11'
- 101
? -
+ 11
----------------------------------------------------------------------
Ran 3 tests in 0.001s
FAILED (failures=1)
Phần "..F" ở trên cho biết, hai test case đầu qua, test case thứ ba thất bại. unittest.main() có thể sử dụng tham số verbosity để kiểm soát mức độ chi tiết của kết quả hiển thị, hỗ cấp 0, 1, 2, mặc định là 1. Lúc này test case qua hiển thị là ".", test case thất bại hiển thị là "F", test case lỗi hiển thị là "E". Sự khác biệt giữa thất bại và lỗi là thất bại luôn do kết quả thực tế và kết quả kỳ vọng không nhất quán dẫn đến ngoại lệ AssertionError. Lỗi chỉ là test case chạy gặp các ngoại lệ khác không bắt được khiến việc thực thi test case bị gián đoạn.
Khi test case thất bại hoặc lỗi sẽ hiển thị thông tin truy vết chi tiết và kết quả thực tế trong quá trình chạy với kết quả kỳ vọng. Như ví dụ trên, tại chỗ assertion đã hiển thị kết quả thực tế là "101" trong khi kết quả kỳ vọng là "11", dẫn đến assertion thất bại.
Phần cuối là thông tin tóm tắt chạy. Như ví dụ trên, tổng cộng chạy 3 test case, thời gian 0.001 giây, trạng thái tổng thể là thất bại, có 1 test case thất bại.
tham số verbosity của unittest.main() là 0, không hiển thị tình hình thực thi của mỗi test case, chỉ hiển thị thông báo lỗi. verbosity=2 thì hiển thị tên hoặc mô tả của mỗi test case và tình hình thực thi.
Lưu ý: Thứ tự thực thi test case không phải theo thứ tự định nghĩa viết, mà là sắp xếp theo mã ASCII của tên phương thức test case rồi thực thi.
Thay đổi unittest.main() thành unittest.main(verbosity=2), chạy lại kết quả như sau.
test_cong_thuc (__main__.KiemThuDemo)
Kiểm thử số thực ... ok
test_cong_nguyen (__main__.KiemThuDemo)
Kiểm thử số nguyên ... ok
test_cong_chuoi (__main__.KiemThuDemo)
Kiểm thử chuỗi ... FAIL
======================================================================
FAIL: test_cong_chuoi (__main__.KiemThuDemo)
Kiểm thử chuỗi
----------------------------------------------------------------------
Traceback (most recent call last):
File "/Users/superhin/Desktop/test_demo.py", line 22, in test_cong_chuoi
self.assertEqual(ket_qua, '11')
AssertionError: '101' != '11'
- 101
? -
+ 11
----------------------------------------------------------------------
Ran 3 tests in 0.001s
FAILED (failures=1)
Trong chế độ kết quả chi tiết verbosity=2, mỗi test case sẽ hiển thị tên phương thức kiểm thử, module và lớp kiểm thử thuộc về, mô tả kiểm thử docstring và trạng thái thực thi. "ok" có nghĩa là qua, "FAIL" có nghĩa là thất bại.
Chú: Ngoài việc sử dụng lớp để viết test case, unittest còn hỗ trợ chuyển đổi mã kiểm thử dạng hàm thành đối tượng test case, ví dụ như sau.
import unittest
def test_cong_khong():
ket_qua = 0 + 0
assert ket_qua == 0
testcase = unittest.FunctionTestCase(test_cong_khong, setup=..., teardown=...)
Thông thường, khuyến nghị sử dụng cách viết lớp kiểm thử.
Phương thức assertion
Bảng 9.1 Các phương thức assertion thông dụng của TestCase
| Phương thức assertion | Giải thích | Ví dụ |
|---|---|---|
| assertEqual(a, b) | Khẳng định a và b bằng nhau | self.assertEqual(1+1, 2) |
| assertNotEqual(a, b) | Khẳng định a và b không bằng nhau | self.assertEqual(1+2, 2) |
| assertTrue(x) | Khẳng định x là True | self.assertTrue(2>1) |
| assertFalse(x) | Khẳng định x là False | self.assertTrue(2<1) |
| assertIs(a, b) | Khẳng định a và b là cùng một đối tượng | self.assertIs(1, 1) |
| assertIsNot(a, b) | Khẳng định a và b không phải cùng một đối tượng | self.assertIsNot(1, True) |
| assertIsNone(x) | Khẳng định x là None | self.assertIsNone({}.get('a'), None) |
| assertIsNotNone(x) | Khẳng định x không phải None | self.assertIsNotNone({'a':1}.get('a'), None) |
| assertIn(a, b) | Khẳng định a trong b | self.assertIn('a', 'abc') |
| assertNotIn(a, b) | Khẳng định a không trong b | self.assertNotIn(1, \[2,3,4\]) |
| assertIsInstance(a, b) | Khẳng định a là kiểu b | self.assertIsInstance(1, int) |
| assertNotIsInstance(a, b) | Khẳng định a không phải kiểu b | self.assertNotIsInstance(\[\], dict) |
Test case con subTest và ddt dữ liệu驱动
Trong kiểm thử, sử dụng dữ liệu kiểm thử khác nhau cho cùng một quy trình kiểm thử là rất cần thiết. Khi cùng một test case cần kiểm thử nhiều bộ dữ liệu, trong vòng lặp để防止 một bộ dữ liệu kiểm thử thất bại dẫn đến việc thực thi gián đoạn và các test case sau không được kiểm thử, có thể sử dụng subTest trong vòng lặp để đảm bảo tất cả dữ liệu đều được thực thi. Ví dụ như sau.
import unittest
dulieu = [1,2,3,4,5]
class KiemThuVd2(unittest.TestCase):
def test_lon_hon(self):
"""Kiểm thử số lớn hơn 2"""
for item in dulieu:
with self.subTest(item=item):
self.assertGreater(item, 2)
if __name__ == '__main__':
unittest.main(verbosity=2)
Khi chạy sẽ hiển thị tất cả dữ liệu thất bại.
test_a (__main__.KiemThuVd2)
Kiểm thử số lớn hơn 2 ...
======================================================================
FAIL: test_a (__main__.KiemThuVd2) (item=1)
Kiểm thử số lớn hơn 2
----------------------------------------------------------------------
Traceback (most recent call last):
File "/Users/superhin/Desktop/test_demo2.py", line 10, in test_a
self.assertGreater(item, 2)
AssertionError: 1 not greater than 2
======================================================================
FAIL: test_a (__main__.KiemThuVd2) (item=2)
Kiểm thử số lớn hơn 2
----------------------------------------------------------------------
Traceback (most recent call last):
File "/Users/superhin/Desktop/test_demo2.py", line 10, in test_a
self.assertGreater(item, 2)
AssertionError: 2 not greater than 2
----------------------------------------------------------------------
Ran 1 test in 0.001s
FAILED (failures=2)
Sau khi sử dụng subTest, nhiều dữ liệu vẫn biểu hiện là một test case, nếu muốn một test case tạo động thành nhiều test case theo các dữ liệu khác nhau, có thể sử dụng thư viện bên thứ ba ddt để thực hiện. Cách cài đặt như sau.
pip install ddt
Ví dụ sử dụng như sau.
import unittest
import ddt
dulieu = [1,2,3,4,5]
@ddt.ddt
class KiemThuVd3(unittest.TestCase):
@ddt.data(*dulieu) # *dulieu giải danh sách thành nhiều dữ liệu
def test_lon_hon(self, item): # item dùng để nhận mỗi dữ liệu trong @ddt.data
"""Kiểm thử số lớn hơn 2"""
self.assertGreater(item, 2)
if __name__ == '__main__':
unittest.main(verbosity=2)
Để sử dụng ddt cần thêm decorator @ddt.ddt trên lớp kiểm thử, thêm decorator @ddt.data(dữ liệu1, dữ liệu2, dữ liệu3, ...) trên phương thức kiểm thử cần dữ liệu驱动, thêm tham số trong test case để nhận mỗi dữ liệu.
Khi chạy sẽ tạo động nhiều test case, kết quả chạy thực tế như sau.
test_lon_hon_1_1 (__main__.KiemThuVd3)
Kiểm thử số lớn hơn 2 ... FAIL
test_lon_hon_2_2 (__main__.KiemThuVd3)
Kiểm thử số lớn hơn 2 ... FAIL
test_lon_hon_3_3 (__main__.KiemThuVd3)
Kiểm thử số lớn hơn 2 ... ok
test_lon_hon_4_4 (__main__.KiemThuVd3)
Kiểm thử số lớn hơn 2 ... ok
test_lon_hon_5_5 (__main__.KiemThuVd3)
Kiểm thử số lớn hơn 2 ... ok
======================================================================
FAIL: test_lon_hon_1_1 (__main__.KiemThuVd3)
Kiểm thử số lớn hơn 2
----------------------------------------------------------------------
Traceback (most recent call last):
File "/usr/local/lib/python3.7/site-packages/ddt.py", line 182, in wrapper
return func(self, *args, **kwargs)
File "/Users/superhin/Desktop/test_demo3.py", line 11, in test_lon_hon
self.assertGreater(item, 2)
AssertionError: 1 not greater than 2
======================================================================
FAIL: test_lon_hon_2_2 (__main__.KiemThuVd3)
Kiểm thử số lớn hơn 2
----------------------------------------------------------------------
Traceback (most recent call last):
File "/usr/local/lib/python3.7/site-packages/ddt.py", line 182, in wrapper
return func(self, *args, **kwargs)
File "/Users/superhin/Desktop/test_demo3.py", line 11, in test_lon_hon
self.assertGreater(item, 2)
AssertionError: 2 not greater than 2
----------------------------------------------------------------------
Ran 5 tests in 0.001s
FAILED (failures=2)
Đối với dữ liệu lồng nhau, như.
import unittest
import ddt
dulieu = [(1,2,3), (0, 0, 1), (1.1, 2.0, 3), (1, -1, 0)]
@ddt.ddt
class KiemThuVd4(unittest.TestCase):
@ddt.data(*dulieu)
def test_cong(self, item):
"""Kiểm thử cộng"""
a, b, ket_qua = item
self.assertEqual(a+b, ket_qua)
if __name__ == '__main__':
unittest.main(verbosity=2)
Cũng có thể sử dụng @ddt.unpack để giải, tự động chia mỗi bộ dữ liệu thành nhiều biến, ví dụ như sau.
import unittest
import ddt
dulieu = [(1,2,3), (0, 0, 1), (1.1, 2.0, 3), (1, -1, 0)]
@ddt.ddt
class KiemThuVd4(unittest.TestCase):
@ddt.data(*dulieu)
@ddt.unpack
def test_cong(self, a, b, ket_qua):
"""Kiểm thử cộng"""
self.assertEqual(a+b, ket_qua)
if __name__ == '__main__':
unittest.main(verbosity=2)
Hai ví dụ trên có hiệu quả chạy giống nhau.
Bỏ qua test case và kỳ vọng thất bại
Unittest test case hỗ trợ bỏ qua test case hoặc toàn bộ lớp kiểm thử khi một số điều kiện không được đáp ứng. Có thể đặt bỏ qua vô điều kiện (ví dụ: chức năng chưa thực hiện, đã loại bỏ hoặc lỗi chưa sửa) hoặc bỏ theo điều kiện (ví dụ: điều kiện môi trường không đủ, bước phụ thuộc thất bại, v.v.), ví dụ từ trang chủ như sau.
class KiemThuTruongHopCuaToi(unittest.TestCase):
@unittest.skip("demonstrating skipping")
def test_khong_gi(self):
self.fail("shouldn't happen")
@unittest.skipIf(thuvien.__version__ < (1, 3),
"not supported in this library version")
def test_dinh_dang(self):
# Tests that work for only a certain version of the library.
pass
@unittest.skipUnless(sys.platform.startswith("win"), "requires Windows")
def test_ho_tro_windows(self):
# windows specific testing code
pass
def test_co_bo_qua(self):
if not nguyen_lieu_ngoai_khong_san_sang():
self.skipTest("external resource not available")
# test code that depends on the external resource
pass
Kết quả chạy như sau.
test_dinh_dang (__main__.KiemThuTruongHopCuaToi) ... skipped 'not supported in this library version'
test_khong_gi (__main__.KiemThuTruongHopCuaToi) ... skipped 'demonstrating skipping'
test_co_bo_qua (__main__.KiemThuTruongHopCuaToi) ... skipped 'external resource not available'
test_ho_tro_windows (__main__.KiemThuTruongHopCuaToi) ... skipped 'requires Windows'
----------------------------------------------------------------------
Ran 4 tests in 0.005s
OK (skipped=4)
Một số test case là test case ngược, tức là trong điều kiện bình thường test case nên thực thi thất bại, lúc này thực tế là phù hợp với kỳ vọng của chúng ta (kiểm thử qua), để hiển thị kết quả chính xác, chúng ta cần đánh dấu test case là kỳ vọng thất bại, ví dụ như sau.
class KiemThuKyVongThatBai(unittest.TestCase):
@unittest.expectedFailure
def test_that_bai(self):
self.assertEqual(1, 0, "broken")
Chuẩn bị và dọn dẹp kiểm thử
Trong kiểm thử, chuẩn bị và dọn dẹp kiểm thử là một phần không thể thiếu khi thực thi kiểm thử. Chuẩn bị kiểm thử thường được gọi là setUp, dọn dẹp kiểm thử thường được gọi là tearDown (thường còn được dịch là tháo rời). Unittest cung cấp ba phương pháp chuẩn bị và dọn dẹp kiểm thử ở các phạm vi khác nhau, lần lượt là.
- setUp()、tearDown():cấp phương thức kiểm thử (test case), được thực thi trước và sau khi thực thi mỗi test case trong lớp.
- setUpClass()、tearDownClass():cấp lớp kiểm thử, được thực thi một lần trước khi tất cả test case trong lớp được thực thi, và một lần sau khi tất cả được thực thi.
- setUpModule()、tearDownModule():cấp module kiểm thử, được thực thi một lần trước khi tất cả test case trong module kiểm thử này được thực thi, một lần sau khi tất cả được thực thi. Ví dụ như sau.
import unittest
def setUpModule(): # Thực thi một lần trước tất cả test case trong module kiểm thử hiện tại
print('=== Chuẩn bị cấp module ===')
def tearDownModule(): # Thực thi một lần sau tất cả test case trong module kiểm thử hiện tại
print('=== Dọn dẹp cấp module ===')
class KiemThuVd5(unittest.TestCase):
@classmethod
def setUpClass(cls): # Lớp hiện tại, thực thi trước khi tất cả test case, chỉ thực thi một lần
print('--- Chuẩn bị cấp lớp kiểm thử ---')
@classmethod
def tearDownClass(cls): # Lớp hiện tại, thực thi sau khi tất cả test case, chỉ thực thi một lần
print('--- Dọn dẹp cấp lớp kiểm thử ---')
def setUp(self): # Lớp hiện tại, mỗi test case thực thi trước thực thi một lần
print('... Chuẩn bị cấp phương thức kiểm thử ...')
def tearDown(self): # Lớp hiện tại, mỗi test case thực thi sau thực thi một lần
print('... Dọn dẹp cấp phương thức kiểm thử ...')
def _dọn_dẹp_test_a(self):
print('Thực thi dọn dẹp test_a')
def test_a(self):
print('Thực thi chuẩn bị test_a') # Phương pháp chuẩn bị kiểm thử riêng của test case, có thể đặt trong phương thức test case,
print('Thực thi test_a')
self.addCleanup(self._dọn_dẹp_test_a)
def test_b(self):
print('Thực thi test_b')
if __name__ == "__main__":
unittest.main(verbosity=0)
Trong đó setUpModule và tearDownModule là hàm trong module, không tham số không trả về. setUpClass và tearDownClass phải là phương thức lớp, sử dụng decorator @classmethod. setUp và tearDown là phương thức thực thể (đối tượng) bình thường.
Phương pháp chuẩn bị kiểm thử riêng của test case có thể viết trong phương thức test case, trước các bước chính. Phương pháp dọn dẹp kiểm thử riêng của test case thì nên sử dụng self.addCleanup(function, *args, **kwargs) để thêm, để防止 assertion thất bại hoặc bước kiểm thử bất thường dẫn đến phương pháp dọn dẹp không được thực thi.
Kết quả chạy như sau.
=== Chuẩn bị cấp module ===
--- Chuẩn bị cấp lớp kiểm thử ---
... Chuẩn bị cấp phương thức kiểm thử ...
Thực thi chuẩn bị test_a
Thực thi test_a
... Dọn dẹp cấp phương thức kiểm thử ...
Thực thi dọn dẹp test_a
... Chuẩn bị cấp phương thức kiểm thử ...
Thực thi test_b
... Dọn dẹp cấp phương thức kiểm thử ...
--- Dọn dẹp cấp lớp kiểm thử ---
=== Dọn dẹp cấp module ===
----------------------------------------------------------------------
Ran 2 tests in 0.001s
Lưu ý: setUpModule、tearDownModule、setUpClass、tearDownClass、setUp、tearDown là từ khóa cố định, phân biệt chữ hoa chữ thường, tên không được viết sai. Khi setUp、setUpClass、setUpModule có ngoại lệ, test case trong phạm vi đó sẽ không được thực thi. Dù test case có ngoại lệ hay không, tearDown、tearDownClass、tearDownModule đều sẽ được thực thi.
Tổ chức và tải test case
Thực tế test case hiếm khi được thực thi riêng lẻ. Đôi khi chúng ta cần chạy tất cả test case, hoặc thường chúng ta cần lựa chọn một lô test case để thực thi. Để thực hiện việc lựa chọn test case vượt qua lớp kiểm thử, vượt qua module kiểm thử, đã giới thiệu khái niệm TestSuite test suite. TestLoader có thể nhanh chóng tải module kiểm thử, lớp kiểm thử và một lô tên test case để tạo đối tượng test suite.
Tổ chức test case TestSuite
TestSuite là tập hợp test case có thứ tự, đồng thời lồng con suite, tức là có thể nhanh chóng kết hợp hai test suite khác nhau thành một test suite mới. TestSuite có thể sử dụng addTest để thêm test case hoặc con suite, hoặc sử dụng addTests để thêm nhiều test case hoặc con suite cùng một lúc. Ngoài ra cũng có thể truyền vào danh sách test case và con suite khi thực thể hóa để thêm. Ví dụ như sau.
import unittest
class KiemThuVd6(unittest.TestCase):
def test_01(self):
pass
def test_02(self):
pass
def test_03(self):
pass
suite1 = unittest.TestSuite()
suite1.addTest(KiemThuVd6('test_02')) # addTest thêm test case
suite2 = unittest.TestSuite()
suite2.addTest(suite1) # addTest cũng có thể thêm con suite
suite2.addTests([KiemThuVd6('test_03'), KiemThuVd6('test_01')]) # addTest thêm hàng loạt test case hoặc con suite
suite3 = unittest.TestSuite([suite1, suite2, KiemThuVd6('test_03')]) # Khi thực thể hóa suite truyền vào danh sách test case và con suite
Lưu ý: Khác với unittest.main() thực thi test case theo thứ tự mã ASCII của tên phương thức test case, test suite là thực thi tuần tự theo thứ tự thêm.
Tải test case TestLoader
TestLoader tức là trình tải test case, dùng để thu thập test case và tạo đối tượng test suite. Các phương pháp thông dụng như sau.
- discover(start_dir, pattern='test*.py', top_level_dir=None):Thu thập test case theo thư mục đệ quy, từ thư mục start_dir và tất cả các con gói, thu thập test case trong các tệp script được pattern chỉ định, và tạo đối tượng test suite có cấu trúc lồng nhau.
- loadTestsFromModule(module, pattern=None):Tải tất cả test case trong một module kiểm thử, tạo đối tượng test suite.
- loadTestsFromTestCase(testCaseClass):Tải tất cả test case trong một lớp kiểm thử, tạo đối tượng test suite
- loadTestsFromName(name, module=None):Thu thập test case theo tên mô tả, tạo đối tượng test suite.
- loadTestsFromNames(names, module=None) Thu thập test case theo nhiều tên mô tả, tạo đối tượng test suite.
Ví dụ sử dụng như sau.
# Tên tệp: test_demo7.py
import unittest
class KiemThuVd7(unittest.TestCase):
def test_01(self):
pass
def test_02(self):
pass
def test_03(self):
pass
if __name__ == "__main__":
loader = unittest.TestLoader()
suite1 = loader.discover('.') # Thu thập tất cả test case trong thư mục hiện tại và con gói mặc định bắt đầu bằng test
suite2 = loader.loadTestsFromModule('test_demo7') # Thu thập module này cũng hỗ trợ sử dụng __name__
suite3 = loader.loadTestsFromTestCase(KiemThuVd7)
suite4 = loader.loadTestsFromName('test_demo7.KiemThuVd7.test_01')
suite4 = loader.loadTestsFromNames(
['test_demo7.KiemThuVd7.test_02', 'test_demo7.KiemThuVd7.test_03'])
Trong ví dụ trên đã trình bày một số phương pháp sử dụng của trình tải test case. Ngoài loadTestsFromTestCase là sử dụng đối tượng lớp, các phương thức khác đều sử dụng chuỗi mô tả để mô tả module hoặc đường dẫn tìm test case cần nhập. discover thường được sử dụng nhiều hơn, có thể nhanh duyệt tất cả test case trong một thư mục và test case trong con gói, chú ý con gói phải chứa tệp init.py mới được duyệt.
Chạy test case
Ngoài việc sử dụng unittest.main() để chạy tất cả test case trong module kiểm thử khi gỡ lỗi, còn có thể sử dụng giao diện dòng lệnh của unittest để phát hiện và chạy test case. Hoặc tự viết script chạy, sử dụng đối tượng TestRunner để chạy test suite đã组装好.
Chạy bằng dòng lệnh
Vì unittest không tạo tệp thực thi riêng, nên gọi unittest có thể sử dụng python -m unittest hoặc python3 -m unittest. Các lệnh thông dụng như sau.
- python -m unittest tests:Duyệt và chạy tất cả test case trong thư mục tests và con gói
- python -m unittest discover tests:Như trên, duyệt và chạy tất cả test case trong thư mục tests và con gói, hỗ trợ tham số discover.
- python3 -m unittest tests/test_demo5.py:Thực thi tất cả test case trong test_demo5.py trong thư mục tests.
- python3 -m unittest tests.test_demo5 tests.test_demo6:Thực thi nhiều module kiểm thử
- python3 -m unittest tests.test_demo5.KiemThuVd5:Thực thi lớp kiểm thử được chỉ định trong module kiểm thử được chỉ định.
- python3 -m unittest tests.test_demo5.KiemThuVd5.test_a:Thực thi test case được chỉ định trong module kiểm thử được chỉ định, lớp kiểm thử được chỉ định.
Dòng lệnh unittest còn hỗ trợ các tham số chạy sau.
- -v:Chế độ kết quả chi tiết.
- -b,--buffer:Bắt thông tin print trong test case và xuất độc lập.
- -f,--failfast:Gặp lỗi hoặc thất bại dừng ngay (thay vì chạy hết tất cả test case).
- -k:Chạy test case trong đường dẫn (gói.module.lớc.phương thức kiểm thử) chứa chuỗi được chỉ định, phân biệt chữ hoa chữ thường, hỗ trợ * ký tự đại diện, như -k foo có thể khớp với foo_tests.SomeTest.test_something và bar_tests.SomeTest.test_foo, nhưng không khớp với bar_tests.FooTest.test_something.
- --local:Trong thông báo lỗi truy vết, hiển thị biến cục bộ của phương thức kiểm thử.
Chạy bằng code
Chạy bằng code tức là sử dụng script chạy tùy chỉnh, tự组装 test suite, và sử dụng TestRunner để chạy. Ví dụ cơ bản như sau.
import unittest
def chay():
suite = unittest.defaultTestLoader.discover('.', pattern='test*.py')
runner = unittest.TextTestRunner(verbosity=2)
runner.run(suite)
if __name__ == "__main__":
chay()
Tạo báo cáo HTML
Vì TextTestRunner mặc định chỉ có thể xuất báo cáo kiểm thử dạng văn bản, để tạo báo cáo dạng HTML chúng ta cần plugin bên thứ ba, các plugin liên quan khá nhiều, như tệp đơn HTMLTestRunner.py, thư viện bên thứ ba có thể cài đặt trực tiếp htmlrunner, htmlreport, v.v., ở đây lấy htmlrunner làm ví dụ để giải thích. Cách cài đặt là.
pip install htmlrunner
Cách sử dụng đơn giản như sau.
import unittest
import htmlrunner
def chay():
suite = unittest.defaultTestLoader.discover('.', pattern='test*.py')
runner = htmlrunner.HTMLRunner(report_file='%Y%M%d.html', title='Báo cáo kiểm thử', description='Mô tả báo cáo kiểm thử')
runner.run(suite)
if __name__ == "__main__":
chay()
Kiểu báo cáo mặc định như hình 9.2所示.
Hình 9.2 Kiểu báo cáo mặc định của htmlrunner