Thông báo: Download 4 khóa học Python từ cơ bản đến nâng cao tại đây.
Tìm hiểu cách sử dụng unittest trong Python
Kiểm thử đơn vị (unit test) là một phần quan trọng trong quá trình phát triển phần mềm, giúp đảm bảo rằng từng phần nhỏ của chương trình hoạt động đúng như mong đợi. Trong bài viết này, bạn sẽ học về khái niệm kiểm thử đơn vị và cách sử dụng mô-đun unittest
của Python để thực hiện kiểm thử đơn vị một cách hiệu quả. unittest
là một mô-đun mạnh mẽ và linh hoạt, giúp bạn dễ dàng viết, quản lý và chạy các kiểm thử tự động, từ đó phát hiện và khắc phục sớm các lỗi trong mã nguồn, cải thiện chất lượng và độ tin cậy của phần mềm. Qua các ví dụ minh họa, bạn sẽ hiểu rõ hơn về cách thức triển khai và lợi ích của kiểm thử đơn vị trong dự án của mình.
![banquyen png](https://freetuts.net/public/banquyen.png)
Kiểm thử đơn vị là gì?
Kiểm thử đơn vị là một dạng kiểm thử tự động:
- Xác minh một phần nhỏ của mã gọi là đơn vị (unit). Đơn vị có thể là một hàm hoặc phương thức của một lớp.
- Chạy rất nhanh.
- Thực hiện trong một môi trường cô lập.
Mục đích của kiểm thử đơn vị là kiểm tra từng phần nhỏ của chương trình để đảm bảo chúng hoạt động chính xác. Nó khác với kiểm thử tích hợp, là kiểm tra các phần khác nhau của chương trình hoạt động tốt cùng nhau.
Mục tiêu của kiểm thử đơn vị là tìm ra lỗi. Ngoài ra, kiểm thử đơn vị có thể giúp tái cấu trúc mã hiện có để làm cho nó dễ kiểm thử hơn và mạnh mẽ hơn.
Bài viết này được đăng tại [free tuts .net]
Python cung cấp cho bạn một mô-đun tích hợp sẵn là unittest, cho phép bạn thực hiện kiểm thử đơn vị một cách hiệu quả.
Thuật ngữ xUnit trong Python
Mô-đun unittest tuân theo triết lý xUnit. Nó có các thành phần chính sau:
- System under test: Là một hàm, một lớp hoặc một phương thức sẽ được kiểm thử.
- Test case class (unittest.TestCase): Là lớp cơ sở cho tất cả các lớp kiểm thử. Nói cách khác, tất cả các lớp kiểm thử là các lớp con của lớp TestCase trong mô-đun unittest.
- Test fixtures: Là các phương thức thực thi trước và sau khi một phương thức kiểm thử thực thi.
- Assertions: Là các phương thức kiểm tra hành vi của thành phần đang được kiểm thử.
- Test suite: Là một nhóm các bài kiểm thử liên quan được thực thi cùng nhau.
- Test runner: Là một chương trình chạy test suite.
Ví dụ unittest Python
Giả sử bạn có lớp Square
có thuộc tính length
và phương thức area()
trả về diện tích của hình vuông. Lớp Square
nằm trong tệp square.py
:
class Square: def __init__(self, length) -> None: self.length = length def area(self): return self.length * self.length
Để kiểm thử lớp Square
, bạn tạo một tệp mới gọi là test_square.py
và nhập mô-đun unittest
như sau:
import unittest
Vì test_square.py
cần truy cập vào lớp Square
, bạn phải nhập nó từ tệp square.py
:
import unittest from square import Square
Để tạo một trường hợp kiểm thử, bạn định nghĩa một lớp mới gọi là TestSquare
kế thừa từ lớp TestCase
của mô-đun unittest
:
class TestSquare(unittest.TestCase): pass
Để kiểm thử phương thức area()
, bạn thêm một phương thức gọi là test_area()
vào lớp TestSquare
như sau:
import unittest from square import Square class TestSquare(unittest.TestCase): def test_area(self): square = Square(10) area = square.area() self.assertEqual(area, 100)
Trong phương thức test_area()
:
- Đầu tiên, tạo một đối tượng mới của lớp
Square
và khởi tạo thuộc tínhlength
với giá trị 10. - Thứ hai, gọi phương thức
area()
trả về diện tích của hình vuông. - Thứ ba, gọi phương thức
assertEqual()
để kiểm tra kết quả trả về bởi phương thứcarea()
có bằng với diện tích mong đợi (100) hay không.
Nếu diện tích bằng 100, phương thức assertEqual()
sẽ thông qua kiểm thử. Ngược lại, nếu không, phương thức assertEqual()
sẽ thất bại.
Trước khi chạy kiểm thử, bạn cần gọi hàm main()
của mô-đun unittest
như sau:
import unittest from square import Square class TestSquare(unittest.TestCase): def test_area(self): square = Square(10) area = square.area() self.assertEqual(area, 100) if __name__ == '__main__': unittest.main()
Để chạy kiểm thử, bạn mở terminal, điều hướng đến thư mục chứa tệp và thực thi lệnh sau:
python test_square.py
Nếu bạn dùng Linux hoặc macOS, bạn cần sử dụng lệnh python3
thay thế:
python3 test_square.py
Nó sẽ xuất ra kết quả sau:
. ---------------------------------------------------------------------- Ran 1 test in 0.000s OK
Kết quả chỉ ra rằng một kiểm thử đã thông qua, được biểu diễn bởi dấu chấm (.). Nếu kiểm thử thất bại, bạn sẽ thấy chữ F
thay cho dấu chấm (.).
Để có thêm thông tin chi tiết về kết quả kiểm thử, bạn truyền đối số verbosity
với giá trị 2 vào hàm unittest.main()
:
import unittest from square import Square class TestSquare(unittest.TestCase): def test_area(self): square = Square(10) area = square.area() self.assertEqual(area, 100) if __name__ == '__main__': unittest.main(verbosity=2)
Nếu bạn chạy lại kiểm thử:
python test_square.py
Bạn sẽ nhận được thông tin chi tiết:
test_area (__main__.TestSquare) ... ok ---------------------------------------------------------------------- Ran 1 test in 0.001s OK
Kết quả lần này liệt kê trường hợp kiểm thử với kết quả ok
thay vì dấu chấm (.).
Chạy kiểm thử mà không gọi hàm unittest.main() trong Python
Đầu tiên, loại bỏ khối if
gọi hàm unittest.main()
:
import unittest from square import Square class TestSquare(unittest.TestCase): def test_area(self): square = Square(10) area = square.area() self.assertEqual(area, 100)
Thứ hai, thực thi lệnh sau để chạy kiểm thử:
python3 -m unittest
Lệnh này sẽ tìm kiếm tất cả các lớp kiểm thử có tên bắt đầu bằng Test*
nằm trong tệp test_*
và thực thi các phương thức kiểm thử bắt đầu bằng test*
. Tùy chọn -m
đại diện cho mô-đun.
Trong ví dụ này, lệnh sẽ thực thi phương thức test_area()
của lớp TestSquare
trong tệp kiểm thử test_square.py
.
Nếu bạn dùng macOS hoặc Linux, bạn cần sử dụng lệnh python3
thay thế:
python3 -m unittest
Nó sẽ trả về kết quả như sau:
. ---------------------------------------------------------------------- Ran 1 test in 0.000s OK
Để hiển thị thêm thông tin, bạn có thể thêm tùy chọn -v
vào lệnh trên. v
đại diện cho độ chi tiết (verbosity). Nó giống như gọi hàm unittest.main()
với đối số verbosity
có giá trị 2.
python -m unittest -v
Kết quả:
test_area (test_square.TestSquare) ... ok ---------------------------------------------------------------------- Ran 1 test in 0.000s OK
Kiểm thử ngoại lệ dự kiến trong Python
Constructor của lớp Square
chấp nhận tham số length
. Tham số length
nên là kiểu int
hoặc float
. Nếu bạn truyền một giá trị không thuộc các kiểu này, constructor của Square
nên ném ra ngoại lệ TypeError
.
Để kiểm tra xem constructor của Square
có ném ra ngoại lệ TypeError
hay không, bạn sử dụng phương thức assertRaises()
trong một context manager như sau:
import unittest from square import Square class TestSquare(unittest.TestCase): def test_area(self): square = Square(10) area = square.area() self.assertEqual(area, 100) def test_length_with_wrong_type(self): with self.assertRaises(TypeError): square = Square('10')
Nếu bạn chạy lại kiểm thử, nó sẽ thất bại:
python -m unittest -v
Kết quả:
test_area (test_square.TestSquare) ... ok test_length_with_wrong_type (test_square.TestSquare) ... FAIL ====================================================================== FAIL: test_length_with_wrong_type (test_square.TestSquare) ---------------------------------------------------------------------- Traceback (most recent call last): File "D:\python-unit-testing\test_square.py", line 13, in test_length_with_wrong_type with self.assertRaises(TypeError): AssertionError: TypeError not raised ---------------------------------------------------------------------- Ran 2 tests in 0.001s
Phương thức test_length_with_wrong_type()
mong đợi constructor của Square
ném ra ngoại lệ TypeError
. Tuy nhiên, nó không xảy ra.
Để kiểm thử thông qua, bạn cần ném ra ngoại lệ nếu kiểu của thuộc tính length
không phải là int
hoặc float
trong constructor của Square
:
class Square: def __init__(self, length) -> None: if type(length) not in [int, float]: raise TypeError('Length must be an integer or float') self.length = length def area(self): return self.length * self.length
Bây giờ, tất cả các kiểm thử đều thông qua:
python -m unittest -v
Kết quả:
test_area (test_square.TestSquare) ... ok test_length_with_wrong_type (test_square.TestSquare) ... ok ---------------------------------------------------------------------- Ran 2 tests in 0.001s OK
Ví dụ sau đây thêm một kiểm thử mong đợi ngoại lệ ValueError
nếu length
bằng không hoặc âm:
import unittest from square import Square class TestSquare(unittest.TestCase): def test_area(self): square = Square(10) area = square.area() self.assertEqual(area, 100) def test_length_with_wrong_type(self): with self.assertRaises(TypeError): square = Square('10') def test_length_with_zero_or_negative(self): with self.assertRaises(ValueError): square = Square(0) square = Square(-1)
Nếu bạn chạy kiểm thử, nó sẽ thất bại:
python -m unittest -v
Kết quả:
test_area (test_square.TestSquare) ... ok test_length_with_wrong_type (test_square.TestSquare) ... ok test_length_with_zero_or_negative (test_square.TestSquare) ... FAIL ====================================================================== FAIL: test_length_with_zero_or_negative (test_square.TestSquare) ---------------------------------------------------------------------- Traceback (most recent call last): File "D:\python-unit-testing\test_square.py", line 17, in test_length_with_zero_or_negative with self.assertRaises(ValueError): AssertionError: ValueError not raised ---------------------------------------------------------------------- Ran 3 tests in 0.001s FAILED (failures=1)
Để kiểm thử thông qua, bạn thêm một kiểm tra khác vào constructor của Square
:
class Square: def __init__(self, length) -> None: if type(length) not in [int, float]: raise TypeError('Length must be an integer hoặc float') if length < 0: raise ValueError('Length must not be negative') self.length = length def area(self): return self.length * self length
Bây giờ, tất cả ba kiểm thử đều thông qua:
python -m unittest -v
Kết quả:
test_area (test_square.TestSquare) ... ok test_length_with_wrong_type (test_square.TestSquare) ... ok test_length_with_zero_or_negative (test_square.TestSquare) ... ok ---------------------------------------------------------------------- Ran 3 tests in 0.001s OK
Kết bài
Kiểm thử đơn vị là một phần không thể thiếu trong việc phát triển phần mềm hiện đại, giúp phát hiện sớm các lỗi và đảm bảo từng phần của chương trình hoạt động đúng đắn. Sử dụng mô-đun unittest
của Python, chúng ta có thể dễ dàng tạo ra các kiểm thử tự động, hiệu quả và có tổ chức. Bằng cách kế thừa lớp unittest.TestCase
, bạn có thể xây dựng các trường hợp kiểm thử cụ thể và sử dụng các phương thức như assertEqual()
để kiểm tra tính đúng đắn của các giá trị và assertRaises()
để kiểm tra các ngoại lệ mong đợi. Việc chạy kiểm thử với lệnh python -m unittest -v
cho phép bạn kiểm tra chi tiết kết quả của từng trường hợp kiểm thử, giúp bạn nhanh chóng phát hiện và khắc phục các vấn đề trong mã nguồn.
Thông qua việc áp dụng các kỹ thuật kiểm thử đơn vị, bạn không chỉ nâng cao chất lượng và độ tin cậy của phần mềm mà còn tạo điều kiện thuận lợi cho việc bảo trì và phát triển sau này. Kiểm thử đơn vị không chỉ là một công cụ phát hiện lỗi, mà còn là một phần quan trọng trong quy trình phát triển, giúp bạn tự tin hơn khi thực hiện các thay đổi và cải tiến mã nguồn. Hy vọng rằng qua bài hướng dẫn này, bạn đã nắm vững được cách sử dụng mô-đun unittest
và nhận thấy tầm quan trọng của kiểm thử đơn vị trong dự án phát triển phần mềm của mình.