Cách Viết Kiểm Thử Đơn Vị - Nâng Cao
By JoeVu, at: 19:39 Ngày 22 tháng 2 năm 2023
Thời gian đọc ước tính: __READING_TIME__ phút


Đây là chương tiếp theo của Hướng dẫn viết Unit Test trong Python - Người mới bắt đầu
Trong chương này, chúng ta sẽ đề cập đến một số chủ đề quan trọng
- Khám phá Test
- Thiết lập và dọn dẹp
- Mocking
- Độ phủ sóng + Báo cáo
- Mẹo + Thực tiễn tốt nhất + Hướng dẫn
- Nguyên tắc và khái niệm chính của UnitTest
- Tài liệu tham khảo
Hãy cùng xem xét từng phần một
1. Khám phá Test
Python unittest discover hoạt động bằng cách quét qua tất cả các tệp trong thư mục và các thư mục con của nó và tìm bất kỳ tệp nào khớp với mẫu test. Mẫu test được người dùng xác định và có thể được tùy chỉnh để tìm các test cụ thể cho dự án. Sau khi mẫu test được xác định, công cụ sẽ thực thi tất cả các test khớp với mẫu đó.
Những lợi ích khi sử dụng Python unittest discover là gì?
Sử dụng Python unittest discover có thể giúp các nhà phát triển và người kiểm thử tiết kiệm thời gian và công sức bằng cách đơn giản hóa quy trình tìm kiếm và chạy test
Giả sử chúng ta có một thư mục tests như sau, bạn có thể kiểm tra mã ví dụ ở đây
garage/
cars/
honda.py
test_honda.py
libs/
datetimes.py
test_datetimes.py
Bạn có thể pull mã và vào thư mục gốc để chạy các test
cd garage
python -m unittest discover
Theo mặc định, nó tự động tìm tất cả các tệp test*.py
trong thư mục hiện tại (garage
), chúng ta có thể đạt được hiệu quả tương tự bằng cách
python -m unittest # để chạy tất cả các test trong thư mục garage
python -m unittest discovery libs # để chạy tất cả các test trong thư mục garage/libs
python -m unittest discovery cars # để chạy tất cả các test trong thư mục garage/honda
python -m unittest cars/test_honda.py # để chạy tệp test cars/test_honda.py
python -m unittest cars.test_honda # để chạy tệp test cars/test_honda.py
2. Thiết lập và dọn dẹp
Thiết lập và dọn dẹp là các tính năng quan trọng trong Python unittest, có 4 phương thức
- setUp
- setUpClass
- tearDown
- tearDownClass
Các phương thức setUp()
và tearDown()
được sử dụng để định nghĩa mã chạy trước và sau mỗi phương thức test, trong khi các phương thức setUpClass()
và tearDownClass()
được sử dụng để định nghĩa mã chạy trước và sau toàn bộ bộ test.
Ví dụ: giả sử bạn có một lớp với một tập hợp các test để xác minh hành vi của một phương thức tạo tệp. Bạn có thể sử dụng setUp()
và tearDown()
để tạo và xóa tệp trước và sau mỗi test. Mã có thể trông như thế này:
import os
class TestFileUpload(unittest.TestCase):
def setUp(self):
self.file_name = "temporary.txt"
with open(self.file_name, "w") as fp:
pass
def tearDown(self):
os.remove(self.file_name)
Trong ví dụ này, phương thức setUp()
tạo một tệp mới cho mỗi test, và phương thức tearDown()
xóa nó sau khi test đã chạy.
Trong một số trường hợp, bạn có thể cần thực hiện các thao tác thiết lập và dọn dẹp cho toàn bộ bộ test, thay vì cho mỗi test riêng lẻ. Ví dụ: nếu bạn đang test trên cơ sở dữ liệu, bạn có thể cần thiết lập kết nối cơ sở dữ liệu một lần, trước khi tất cả các test chạy, và sau đó dọn dẹp nó sau khi tất cả các test đã hoàn thành. Điều này có thể được thực hiện bằng các phương thức setUpClass()
và tearDownClass()
, như thế này:
class TestDatabase(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.db_connection = DatabaseConnection("localhost", "test_db")
@classmethod
def tearDownClass(cls):
cls.db_connection.close()
Trong ví dụ này, phương thức setUpClass()
tạo một kết nối cơ sở dữ liệu mới, và phương thức tearDownClass()
đóng nó sau khi tất cả các test đã hoàn thành.
Điều này cực kỳ hữu ích khi bạn làm việc với ứng dụng Django, nơi setUp và setUpClass được sử dụng để tạo dữ liệu test, và tearDown và tearDownClass được sử dụng để dọn dẹp dữ liệu test cho mỗi trường hợp test, mỗi lớp test.
3. Mocking
Mock là các đối tượng mô phỏng bắt chước hành vi của các đối tượng thực tế theo cách được kiểm soát. Chúng được sử dụng trong unit test để cô lập và kiểm soát môi trường mà các test được chạy, và để đơn giản hóa test bằng cách loại bỏ nhu cầu tạo các đối tượng thực tế.
Thư viện unittest cung cấp các phương thức patch
, patch.object
và side_effect
để tạo các đối tượng mock. Phương thức patch
được sử dụng để vá một hàm hoặc lớp, trong khi phương thức patch.object
được sử dụng để vá một thể hiện của một lớp. Phương thức side_effect
cho phép các nhà phát triển chỉ định hành vi của một đối tượng mock, chẳng hạn như nó sẽ trả về gì khi được gọi.
Ví dụ, giả sử có một thư viện api facebook, và chúng ta có một hàm để gọi một api facebook.
tệp facebook_api.py
import requests
class FacebookAPI:
def get_userinfo(self, username):
response = requests.get("https://facebook.com/")
if response.status_code == 200:
return response.json()
return {}
tệp user.py
from libs.facebook_api import FacebookAPI
def is_registered_user(username):
user_info = FacebookAPI().get_userinfo(username)
return user_info != {}
tệp test_user.py
import unittest
from unittest import mock
from libs.facebook_api import FacebookAPI
from libs.user import is_registered_user
class TestDatetimes(unittest.TestCase):
@mock.patch.object(FacebookAPI, "get_userinfo")
def test_is_registered_user_return_true(self, mock_get_userinfo):
mock_get_userinfo.return_value = {"userid": 1}
result = is_registered_user("joe")
self.assertTrue(result)
@mock.patch.object(FacebookAPI, "get_userinfo")
def test_is_registered_user_return_false(self, mock_get_userinfo):
mock_get_userinfo.return_value = {}
result = is_registered_user("joe")
self.assertFalse(result)
Vì FacebookAPI thực hiện yêu cầu đến URL Facebook, mà chúng ta không muốn thực thi trong unittest, nên chúng ta sử dụng mock.
Sử dụng các đối tượng mock trong unittest cho phép các nhà phát triển nhanh chóng và dễ dàng tạo các unit test mà không cần phải tương tác với việc thực hiện thực tế của mã. Các đối tượng mock cung cấp một cách để cô lập và kiểm soát môi trường mà các test được chạy, và đơn giản hóa test bằng cách loại bỏ nhu cầu tạo các đối tượng thực tế.
4. Độ phủ sóng + Báo cáo Test
Python unittest cho phép các nhà phát triển viết test để bao phủ mã nguồn, nhưng nó không hiển thị báo cáo độ phủ sóng (bao nhiêu phần trăm mã nguồn được bao phủ bởi test). Độ phủ sóng mã là một chỉ số quan trọng để đo hiệu quả của unit test. Độ phủ sóng mã đo lượng mã được thực thi bởi các test. Độ phủ sóng càng cao, các test càng tốt.
Gói Coverage là một công cụ phổ biến để đo độ phủ sóng mã trong Python. Nó có thể được sử dụng để đo độ phủ sóng của các test được viết bằng mô-đun Python unittest. Gói Coverage có thể được cài đặt bằng lệnh pip: pip install coverage
Sau khi cài đặt, Coverage có thể được sử dụng để tạo báo cáo về độ phủ sóng mã của các test. Để tạo báo cáo, lệnh Coverage cần được thực thi với các tham số phù hợp. Ví dụ: để tạo báo cáo về độ phủ sóng mã của tất cả các test trong tệp test_module.py, lệnh sau cần được thực thi:
coverage run -m unittest discover
Sau khi lệnh được thực thi, Coverage sẽ tạo một báo cáo về độ phủ sóng mã cho các test. Báo cáo này có thể được xem trong terminal, hoặc nó có thể được ghi vào tệp ở định dạng HTML hoặc XML.
coverage report
Name Stmts Miss Cover
--------------------------------------------
cars/__init__.py 0 0 100%
cars/honda.py 29 0 100%
cars/test_honda.py 41 0 100%
libs/__init__.py 0 0 100%
libs/datetimes.py 7 0 100%
libs/facebook_api.py 7 4 43%
libs/test_datetimes.py 16 0 100%
libs/test_user.py 15 0 100%
libs/user.py 4 0 100%
--------------------------------------------
TOTAL 119 4 97%
Hoặc chúng ta có thể sử dụng
Điều này sẽ ghi báo cáo HTML vào htmlcov/index.html
Điều này sẽ mở một cửa sổ trình duyệt mới để phân tích phần mã nào chưa được test bao phủ.
Báo cáo Coverage là một công cụ vô giá đối với các nhà phát triển. Nó cung cấp cho họ cái nhìn chi tiết về độ phủ sóng mã của các test. Bằng cách phân tích báo cáo, các nhà phát triển có thể xác định các vùng mã của họ chưa được test.
5. Mẹo + Thực tiễn tốt nhất + Hướng dẫn
- Sử dụng Phát triển hướng dẫn bằng Test: Phát triển hướng dẫn bằng Test, hay TDD, là một công cụ mạnh mẽ để phát triển các unit test mạnh mẽ. Nó bao gồm việc viết một test cho mỗi tính năng của mã của bạn trước khi bạn thực sự viết mã đó. Điều này giúp đảm bảo rằng mã đáp ứng tất cả các thông số kỹ thuật và yêu cầu do nhóm phát triển đặt ra.
- Tạo các Test độc lập: Điều quan trọng là tạo ra các test độc lập với nhau. Điều này có nghĩa là mỗi test nên tập trung vào một tính năng hoặc khía cạnh duy nhất của mã, thay vì test tất cả các tính năng cùng một lúc. Điều này đảm bảo rằng bất kỳ lỗi hoặc sự cố nào phát sinh ở một phần của mã sẽ không ảnh hưởng đến các phần khác của mã.
- Viết các Trường hợp Test chi tiết: Viết các trường hợp test chi tiết là một bước quan trọng trong quy trình unit test. Mỗi test nên nêu rõ điều nó đang test và kết quả mong đợi
- Tổ chức các trường hợp test và tệp test: Đối với mỗi tệp trong mã nguồn (facebook_api.py), sẽ có một tệp test cho tệp đó với tiền tố "test_" (test_facebook_api.py). Mỗi hàm/phương thức sẽ có một danh sách các trường hợp test để bao phủ tất cả logic
- test đầu ra: giá trị và định dạng
- test tác dụng phụ: test xử lý ngoại lệ
- đối với mỗi câu lệnh if/else, try/except, cần có một test
- Kiên định và cam kết: Viết unit test tốt cần thời gian và sự cống hiến. Bạn không chỉ cần sửa đổi các test của mình khi thực hiện thay đổi đối với mã, mà bạn cũng có thể phát hiện ra lỗi trong mã test của mình. Mặc dù cần nhiều công sức, nhưng lợi ích của unit test là vô giá, ngay cả đối với các dự án nhỏ. Tuy nhiên, điều quan trọng cần nhớ là những phần thưởng này có giá của nó. Bạn phải kỷ luật. Hãy nhất quán. Hãy chắc chắn rằng các test luôn luôn vượt qua. Đừng để các test bị hỏng vì bạn "biết" mã là ổn.
- Tự động hóa: Để duy trì kỷ luật, bạn nên thiết lập các unit test tự động. Các test này nên được chạy vào những thời điểm quan trọng như pre-commit hoặc pre-deployment. Ngoài ra, sẽ rất hữu ích nếu hệ thống quản lý nguồn của bạn từ chối bất kỳ mã nào không vượt qua tất cả các test của nó. Sử dụng pre-commit https://pre-commit.com/ là lựa chọn tốt nhất ở đây
6. Nguyên tắc và khái niệm chính của UnitTest
6.1 Thiết kế để dễ test
Thiết kế để dễ test là một khái niệm quan trọng trong python unittest. Nó bao gồm việc thiết kế mã dễ dàng để test và có cấu trúc tốt để test.
- mã được mô đun hóa, các hàm có thể test đơn vị
- ghi nhật ký và xử lý lỗi đúng cách.
- viết các test có ý nghĩa, bao phủ tất cả các đường dẫn mã cần thiết và dễ bảo trì.
- sử dụng các công cụ như trình chạy test và tích hợp liên tục để đảm bảo các test được chạy thường xuyên và các test bao phủ tất cả các trường hợp cần thiết.
- tạo mã dễ dàng hơn để test, bảo trì và cải thiện.
6.2 Chi phí/Lợi ích
Với python unittest, các nhà phát triển có thể nhanh chóng và dễ dàng đánh giá chi phí/lợi ích của các test riêng lẻ bằng cách xem xét:
- số dòng mã
- tốc độ thực thi
để có được phương pháp tốt nhất cho dự án của họ, đảm bảo kết quả tốt nhất với ít tài nguyên nhất.
6.3 Tư duy về Test
Tư duy về test nhấn mạnh những điều sau
- Đảm bảo rằng mỗi test đều cụ thể và độc lập với các test khác
- Tạo các khẳng định để xác minh hành vi mong đợi
- Lập kế hoạch cho việc viết test
- Sử dụng phương pháp luận phát triển hướng dẫn bằng test
- Tài liệu hóa kết quả test
6.4 Hàm thuần túy
Hàm thuần túy trong python unittest là các hàm không dựa vào bất kỳ yếu tố bên ngoài nào để tạo ra đầu ra giống nhau. Chúng cũng được gọi là các hàm xác định. Lợi ích của việc sử dụng các hàm như vậy trong unittesting là:
- Dễ dàng viết test hơn vì đầu vào giống nhau luôn tạo ra đầu ra giống nhau
- Không cần phải lo lắng về các yếu tố bên ngoài ảnh hưởng đến kết quả test
- Làm cho test đáng tin cậy và có thể lặp lại hơn
- Giúp duy trì tính nhất quán của mã và giảm lỗi trong thời gian dài
6.5 Test xử lý lỗi
Test xử lý lỗi trong Python unittest là quá trình đảm bảo rằng lỗi được xử lý đúng cách trong mã. Xử lý lỗi là một khía cạnh quan trọng của lập trình và cần thiết để đảm bảo rằng mã chạy như mong đợi. Unittest là một khung test mạnh mẽ được cung cấp bởi Python cho phép các nhà phát triển test mã dễ dàng. Dưới đây là một số cách để test xử lý lỗi trong Python unittest:
- Tạo một trường hợp test gây ra ngoại lệ và xác minh rằng ngoại lệ mong đợi được gây ra.
- Tạo một trường hợp test nhận ngoại lệ mong đợi làm đầu vào và xác minh rằng ngoại lệ mong đợi được gây ra.
- Tạo một trường hợp test nhận ngoại lệ không mong đợi làm đầu vào và xác minh rằng ngoại lệ mong đợi không được gây ra.
- Tạo một trường hợp test kiểm tra thông báo lỗi chính xác khi ngoại lệ được gây ra.
- Tạo một trường hợp test kiểm tra việc xử lý lỗi chính xác.
6.6 Test các phương thức Private
Test các phương thức private trong python unittest là một cách hữu ích để đảm bảo rằng tất cả các phần của chương trình đang hoạt động chính xác. Dưới đây là một vài lợi ích của việc test các phương thức private:
- Phát hiện và sửa lỗi nhanh chóng, vì các phương thức private có thể được test mà không cần phải chạy toàn bộ chương trình.
- Xác định nguồn gốc của sự cố nhanh hơn, vì chúng có thể thu hẹp tìm kiếm xuống một phương thức private cụ thể.
- Thúc đẩy thiết kế mã tốt hơn, vì các phương thức private có thể được test để đảm bảo chúng được viết đúng.
- Tái cấu trúc mã an toàn hơn, vì các test có thể được sử dụng để đảm bảo rằng các phương thức private vẫn hoạt động chính xác sau khi tái cấu trúc.
6.7 Cách tổ chức Unit Test của bạn
Tổ chức unit test trong python unittest là một cách tuyệt vời để đảm bảo rằng mã của bạn được test kỹ lưỡng và hoạt động đúng cách. Dưới đây là một vài mẹo về cách tổ chức unit test của bạn:
- Nhóm các test vào các bộ test logic - Nhóm các test của bạn vào các bộ liên quan đến một phần cụ thể của mã. Điều này sẽ giúp bạn giữ cho các test của mình được sắp xếp và dễ đọc.
- Viết tên test mô tả - Đặt tên test của bạn có tính mô tả giúp giải thích test đang làm gì. Điều này sẽ giúp bạn nhanh chóng xác định các test nào liên quan đến một phần cụ thể của mã.
- Sử dụng setUp và tearDown - Sử dụng các phương thức setUp và tearDown để thiết lập và dọn dẹp bất kỳ tài nguyên nào bạn có thể cần cho các test của mình. Điều này sẽ giúp giảm mã trùng lặp và làm cho các test của bạn dễ đọc hơn.
- Tạo các test dựa trên dữ liệu - Tạo các test dựa trên dữ liệu để test mã của bạn với nhiều bộ dữ liệu. Điều này sẽ giúp đảm bảo rằng mã của bạn hoạt động đúng bất kể dữ liệu nào được nhập vào.
- Sử dụng khẳng định - Sử dụng các câu lệnh khẳng định để đảm bảo rằng mã của bạn đang trả về kết quả mong đợi. Điều này sẽ giúp bạn nhanh chóng xác định bất kỳ sự cố nào với mã của mình.
7. Tài liệu tham khảo
Sách
Test-Driven Development with Python
Liên kết
https://docs.python.org/3/library/unittest.html