PYTHON UNIT TESTING
CÁC CHỦ ĐỀ
BÀI MỚI NHẤT
MỚI CẬP NHẬT

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.

test php

banquyen png
Bài viết này được đăng tại freetuts.net, không được copy dưới mọi hình thức.

Kiểm thử đơn vị là gì?

Screenshot 202024 06 15 20223858 png

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

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ính length 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ức area() 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.

Cùng chuyên mục:

Hướng dẫn xây dựng Command-Line Interface (CLI) bằng Quo trong Python

Hướng dẫn xây dựng Command-Line Interface (CLI) bằng Quo trong Python

Hướng dẫn toàn diện về module datetime trong Python

Hướng dẫn toàn diện về module datetime trong Python

Cách truy cập và thiết lập biến môi trường trong Python

Cách truy cập và thiết lập biến môi trường trong Python

Lớp dữ liệu (Data Classes) trong Python với decorator @dataclass

Lớp dữ liệu (Data Classes) trong Python với decorator @dataclass

Từ khóa yield trong Python

Từ khóa yield trong Python

Sự khác biệt giữa sort() và sorted() trong Python

Sự khác biệt giữa sort() và sorted() trong Python

Sử dụng Poetry để quản lý dependencies trong Python

Sử dụng Poetry để quản lý dependencies trong Python

Định dạng chuỗi Strings trong Python

Định dạng chuỗi Strings trong Python

Một tác vụ phổ biến khi làm việc với danh sách trong Python

Một tác vụ phổ biến khi làm việc với danh sách trong Python

Làm việc với các biến môi trường trong Python

Làm việc với các biến môi trường trong Python

Sự khác biệt giữa set() và frozenset() trong Python

Sự khác biệt giữa set() và frozenset() trong Python

Sự khác biệt giữa iterator và iterable trong Python

Sự khác biệt giữa iterator và iterable trong Python

Cách làm việc với file tarball/tar trong Python

Cách làm việc với file tarball/tar trong Python

Chuyển đổi kiểu dữ liệu trong Python

Chuyển đổi kiểu dữ liệu trong Python

Sự khác biệt giữa toán tử == và is trong Python

Sự khác biệt giữa toán tử == và is trong Python

Làm việc với file ZIP trong Python

Làm việc với file ZIP trong Python

Cách sử dụng ThreadPoolExecutor trong Python

Cách sử dụng ThreadPoolExecutor trong Python

Sự khác biệt giữa byte objects và string trong Python

Sự khác biệt giữa byte objects và string trong Python

Xử lý độ chính xác các hàm floor, ceil, round, trunc, format  trong Python

Xử lý độ chính xác các hàm floor, ceil, round, trunc, format trong Python

Cách lặp qua nhiều list với hàm zip() trong Python

Cách lặp qua nhiều list với hàm zip() trong Python

Top