Làm ngay trong 5 phút: Cài và chạy Pynguin
Giả sử bạn có một file Python với vài hàm tính toán nhưng chưa có test nào. Thay vì ngồi nghĩ “mình cần test case gì”, hãy để AI làm việc đó trước.
Cài Pynguin:
pip install pynguin
Tạo file calculator.py với vài hàm mẫu:
def add(a: int, b: int) -> int:
return a + b
def divide(a: float, b: float) -> float:
if b == 0:
raise ValueError("Không chia được cho 0")
return a / b
def is_palindrome(s: str) -> bool:
s = s.lower().strip()
return s == s[::-1]
Chạy Pynguin để sinh test tự động:
pynguin \
--project-path . \
--module-name calculator \
--output-path tests/
Sau vài chục giây, Pynguin tạo ra tests/test_calculator.py. Kết quả mẫu trông như này:
import pytest
from calculator import add, divide, is_palindrome
def test_add_0():
assert add(0, 0) == 0
def test_add_1():
assert add(1, 2) == 3
def test_divide_0():
assert divide(4.0, 2.0) == pytest.approx(2.0)
def test_divide_raises():
with pytest.raises(ValueError):
divide(1.0, 0.0)
def test_is_palindrome_0():
assert is_palindrome("racecar") is True
def test_is_palindrome_1():
assert is_palindrome("hello") is False
Chạy test và xem coverage:
pytest tests/ -v --cov=calculator --cov-report=term-missing
Xong. Từ không có test nào lên 80–90% coverage trong vài phút.
Pynguin hoạt động như thế nào?
Pynguin dùng thuật toán search-based software testing (SBST) — cụ thể là genetic algorithm để “tiến hóa” các test case qua nhiều thế hệ nhằm đạt branch coverage cao nhất. Nó không random mù quáng, mà tối ưu theo mục tiêu: cover nhiều nhánh code nhất có thể.
Điều mình thấy thú vị khi dùng thực tế là Pynguin khá giỏi phát hiện edge cases — đặc biệt là các nhánh exception. Nó tự thử truyền None, số âm, chuỗi rỗng, float cực lớn… để xem hàm xử lý thế nào. Mấy bug ẩn về type checking mình từng gặp ở code cũ đều bị Pynguin bắt được ngay lần đầu.
Yêu cầu quan trọng: Code phải có type hints. Pynguin phụ thuộc vào type annotations để biết nên sinh giá trị input kiểu gì. Hàm không có a: int hay -> float, kết quả sinh test sẽ rất hạn chế hoặc không sinh được gì.
CodiumAI: Khi AI đọc hiểu logic thay vì chỉ đo coverage
Pynguin giỏi về structural coverage nhưng test của nó khá “máy móc” — tên như test_add_0, giá trị trông vô nghĩa như add(1234567, -9876543). Đây là lúc CodiumAI (nay đổi tên thành Qodo) có lợi thế.
CodiumAI dùng LLM để đọc hiểu business logic của hàm, từ đó sinh test case có ngữ nghĩa — tên test mô tả hành vi, scenario cover các tình huống thực tế hơn.
Cài CodiumAI qua VS Code
Tìm extension “Qodo Gen” (tên cũ: CodiumAI) trên VS Code Marketplace, cài và đăng ký tài khoản free. Sau khi cài xong, mở file Python, click vào tên hàm bất kỳ — nút “Generate Tests” sẽ xuất hiện ngay phía trên hàm đó. Click vào, AI phân tích và hiển thị panel bên phải với các test case được đề xuất.
Cùng hàm divide() ở trên, CodiumAI sinh ra:
class TestDivide:
def test_divide_positive_numbers(self):
"""Chia hai số dương — kết quả chính xác"""
assert divide(10.0, 2.0) == 5.0
def test_divide_negative_dividend(self):
"""Số bị chia âm vẫn tính đúng"""
assert divide(-10.0, 2.0) == -5.0
def test_divide_by_zero_raises_value_error(self):
"""Chia cho 0 phải raise ValueError đúng message"""
with pytest.raises(ValueError, match="Không chia được cho 0"):
divide(5.0, 0.0)
def test_divide_result_precision(self):
"""Kết quả float giữ đúng độ chính xác"""
result = divide(7.0, 2.0)
assert result == pytest.approx(3.5)
Cùng là test cho một hàm, nhưng có tên mô tả rõ scenario, có docstring, check cả error message. Đưa vào code review đồng nghiệp đọc được ngay, không cần giải thích.
Kết hợp cả hai: Chiến lược thực tế
Trong quá trình làm việc thực tế, mình nhận thấy đây là một trong những kỹ năng quan trọng cần nắm — không phải chọn Pynguin hay CodiumAI, mà dùng cả hai theo từng giai đoạn:
- Pynguin — dùng khi cần phủ nhanh coverage cho codebase cũ chưa có test. Chạy một lần, có ngay baseline 60–80%.
- CodiumAI — dùng cho feature mới, khi cần test có ý nghĩa, review được, đưa vào CI/CD mà không xấu hổ.
Workflow thực tế từng bước
# Bước 1: Pynguin quét toàn bộ module
pynguin \
--project-path . \
--module-name myapp.utils \
--output-path tests/auto/ \
--maximum-search-time 120
# Bước 2: Xem coverage hiện tại
pytest tests/ --cov=myapp --cov-report=html
# Bước 3: Mở htmlcov/index.html, xem branch nào còn đỏ
# Bước 4: Dùng CodiumAI để viết test targeted cho đúng branch đó
Tùy chỉnh Pynguin cho code phức tạp
Mặc định Pynguin chạy 600 giây per module. Với module nhiều branch, chuyển sang thuật toán DYNAMOSA:
pynguin \
--project-path . \
--module-name myapp.services.payment \
--output-path tests/auto/ \
--maximum-search-time 300 \
--algorithm DYNAMOSA \
--seed 42
DYNAMOSA (Dynamic Many-Objective Sorting Algorithm) xử lý tốt hơn khi module có nhiều nhánh điều kiện lồng nhau.
Tips thực tế không thể bỏ qua
1. Thêm type hints trước khi chạy Pynguin. Đây là điều kiện tiên quyết. Với codebase cũ không có annotations, dùng mypy --install-types để auto-suggest, hoặc MonkeyType để tự động thêm type hints từ runtime.
2. Review kỹ trước khi commit test AI sinh ra. Pynguin không biết business logic — nó chỉ ghi lại output hiện tại của hàm. Nếu hàm đang có bug, test sẽ assert đúng với bug đó. Mình từng có case test pass hoàn toàn nhưng logic tính discount sai, Pynguin vẫn sinh assert discount == 10 vì đó là output thực tế lúc chạy.
3. Đo coverage trước/sau để thấy hiệu quả rõ ràng:
# Trước
pytest --cov=myapp --cov-report=term | grep TOTAL
# TOTAL 342 289 15%
# Sau khi thêm AI-generated tests
pytest --cov=myapp --cov-report=term | grep TOTAL
# TOTAL 342 62 82%
4. Mock external dependencies trước. Cả Pynguin lẫn CodiumAI đều sinh test kém hiệu quả cho hàm gọi database hay HTTP. Tách phần logic thuần khỏi phần I/O, hoặc dùng mock:
from unittest.mock import patch, MagicMock
def test_fetch_user_with_mock():
with patch("myapp.services.requests.get") as mock_get:
mock_get.return_value = MagicMock(
status_code=200,
json=lambda: {"id": 1, "name": "Test User"}
)
result = fetch_user(1)
assert result["name"] == "Test User"
5. CodiumAI sinh test tốt hơn khi hàm có docstring. Nếu hàm có mô tả rõ behavior và các expected cases, LLM hiểu context và sinh test đúng scenario hơn nhiều so với hàm để trống.
Điểm mạnh thực sự của cả hai tool không phải là thay thế việc viết test — mà là giúp bạn bắt đầu nhanh, phủ coverage cho phần cơ bản, và gợi ý những edge case bạn chưa nghĩ tới. Phần business logic quan trọng, test vẫn nên tự tay viết và review kỹ.

