Khám phá kiểm thử dựa trên thuộc tính với thư viện Hypothesis của Python. Vượt ra ngoài các kiểm thử dựa trên ví dụ để tìm các trường hợp đặc biệt và xây dựng phần mềm mạnh mẽ, đáng tin cậy hơn.
Vượt Ra Ngoài Unit Test: Tìm Hiểu Sâu về Kiểm Thử Dựa Trên Thuộc Tính với Hypothesis của Python
Trong thế giới phát triển phần mềm, kiểm thử là nền tảng của chất lượng. Trong nhiều thập kỷ, mô hình thống trị là kiểm thử dựa trên ví dụ. Chúng ta tỉ mỉ tạo ra các đầu vào, xác định các đầu ra dự kiến và viết các khẳng định để xác minh rằng mã của chúng ta hoạt động như dự định. Cách tiếp cận này, được tìm thấy trong các framework như unittest
và pytest
, rất mạnh mẽ và cần thiết. Nhưng điều gì sẽ xảy ra nếu tôi nói với bạn rằng có một cách tiếp cận bổ sung có thể khám phá ra các lỗi mà bạn thậm chí chưa từng nghĩ đến?
Chào mừng đến với thế giới của kiểm thử dựa trên thuộc tính, một mô hình chuyển trọng tâm từ việc kiểm tra các ví dụ cụ thể sang xác minh các thuộc tính chung của mã của bạn. Và trong hệ sinh thái Python, nhà vô địch không thể tranh cãi của phương pháp này là một thư viện có tên là Hypothesis.
Hướng dẫn toàn diện này sẽ đưa bạn từ một người hoàn toàn mới bắt đầu đến một người thực hành tự tin về kiểm thử dựa trên thuộc tính với Hypothesis. Chúng ta sẽ khám phá các khái niệm cốt lõi, đi sâu vào các ví dụ thực tế và tìm hiểu cách tích hợp công cụ mạnh mẽ này vào quy trình phát triển hàng ngày của bạn để xây dựng phần mềm mạnh mẽ, đáng tin cậy và chống lỗi hơn.
Kiểm Thử Dựa Trên Thuộc Tính Là Gì? Một Sự Thay Đổi Trong Tư Duy
Để hiểu Hypothesis, trước tiên chúng ta cần nắm bắt ý tưởng cơ bản về kiểm thử dựa trên thuộc tính. Hãy so sánh nó với kiểm thử dựa trên ví dụ truyền thống mà tất cả chúng ta đều biết.
Kiểm Thử Dựa Trên Ví Dụ: Con Đường Quen Thuộc
Hãy tưởng tượng bạn đã viết một hàm sắp xếp tùy chỉnh, my_sort()
. Với kiểm thử dựa trên ví dụ, quá trình suy nghĩ của bạn sẽ là:
- "Hãy kiểm tra nó với một danh sách đơn giản, có thứ tự." ->
assert my_sort([1, 2, 3]) == [1, 2, 3]
- "Còn một danh sách có thứ tự ngược lại thì sao?" ->
assert my_sort([3, 2, 1]) == [1, 2, 3]
- "Một danh sách trống thì sao?" ->
assert my_sort([]) == []
- "Một danh sách có các mục trùng lặp?" ->
assert my_sort([5, 1, 5, 2]) == [1, 2, 5, 5]
- "Và một danh sách có số âm?" ->
assert my_sort([-1, -5, 0]) == [-5, -1, 0]
Điều này hiệu quả, nhưng nó có một hạn chế cơ bản: bạn chỉ kiểm tra các trường hợp bạn có thể nghĩ ra. Các bài kiểm tra của bạn chỉ tốt như trí tưởng tượng của bạn. Bạn có thể bỏ lỡ các trường hợp đặc biệt liên quan đến các số rất lớn, sự không chính xác của số dấu phẩy động, các ký tự unicode cụ thể hoặc các tổ hợp dữ liệu phức tạp dẫn đến hành vi không mong muốn.
Kiểm Thử Dựa Trên Thuộc Tính: Tư Duy Về Các Bất Biến
Kiểm thử dựa trên thuộc tính đảo ngược kịch bản. Thay vì cung cấp các ví dụ cụ thể, bạn xác định thuộc tính hoặc bất biến của hàm của bạn—các quy tắc phải đúng cho bất kỳ đầu vào hợp lệ nào. Đối với hàm my_sort()
của chúng ta, các thuộc tính này có thể là:
- Đầu ra được sắp xếp: Đối với bất kỳ danh sách số nào, mọi phần tử trong danh sách đầu ra đều nhỏ hơn hoặc bằng phần tử theo sau nó.
- Đầu ra chứa các phần tử giống như đầu vào: Danh sách đã sắp xếp chỉ là một hoán vị của danh sách ban đầu; không có phần tử nào được thêm hoặc bị mất.
- Hàm là lũy đẳng: Sắp xếp một danh sách đã được sắp xếp không được thay đổi nó. Đó là,
my_sort(my_sort(some_list)) == my_sort(some_list)
.
Với phương pháp này, bạn không viết dữ liệu kiểm tra. Bạn đang viết các quy tắc. Sau đó, bạn cho phép một framework, như Hypothesis, tạo ra hàng trăm hoặc hàng nghìn đầu vào ngẫu nhiên, đa dạng và thường xảo quyệt để cố gắng chứng minh các thuộc tính của bạn là sai. Nếu nó tìm thấy một đầu vào phá vỡ một thuộc tính, nó đã tìm thấy một lỗi.
Giới Thiệu Hypothesis: Trình Tạo Dữ Liệu Kiểm Tra Tự Động Của Bạn
Hypothesis là thư viện kiểm thử dựa trên thuộc tính hàng đầu cho Python. Nó lấy các thuộc tính bạn xác định và thực hiện công việc khó khăn là tạo dữ liệu kiểm tra để thử thách chúng. Nó không chỉ là một trình tạo dữ liệu ngẫu nhiên; nó là một công cụ thông minh và mạnh mẽ được thiết kế để tìm lỗi một cách hiệu quả.
Các Tính Năng Chính của Hypothesis
- Tạo Trường Hợp Kiểm Thử Tự Động: Bạn xác định *hình dạng* của dữ liệu bạn cần (ví dụ: "một danh sách các số nguyên", "một chuỗi chỉ chứa các chữ cái", "một datetime trong tương lai"), và Hypothesis tạo ra nhiều ví dụ khác nhau tuân theo hình dạng đó.
- Thu Hẹp Thông Minh: Đây là tính năng kỳ diệu. Khi Hypothesis tìm thấy một trường hợp kiểm thử thất bại (ví dụ: một danh sách gồm 50 số phức làm sập hàm sắp xếp của bạn), nó không chỉ báo cáo danh sách khổng lồ đó. Nó đơn giản hóa đầu vào một cách thông minh và tự động để tìm ví dụ nhỏ nhất có thể vẫn gây ra lỗi. Thay vì một danh sách gồm 50 phần tử, nó có thể báo cáo rằng lỗi xảy ra chỉ với
[inf, nan]
. Điều này làm cho việc gỡ lỗi cực kỳ nhanh chóng và hiệu quả. - Tích Hợp Liền Mạch: Hypothesis tích hợp hoàn hảo với các framework kiểm thử phổ biến như
pytest
vàunittest
. Bạn có thể thêm các kiểm thử dựa trên thuộc tính cùng với các kiểm thử dựa trên ví dụ hiện có mà không cần thay đổi quy trình làm việc của bạn. - Thư Viện Chiến Lược Phong Phú: Nó đi kèm với một bộ sưu tập lớn các "chiến lược" tích hợp để tạo mọi thứ từ số nguyên và chuỗi đơn giản đến các cấu trúc dữ liệu lồng nhau phức tạp, datetime nhận biết múi giờ và thậm chí cả mảng NumPy.
- Kiểm Thử Có Trạng Thái: Đối với các hệ thống phức tạp hơn, Hypothesis có thể kiểm tra các chuỗi hành động để tìm lỗi trong các chuyển đổi trạng thái, điều mà cực kỳ khó khăn với kiểm thử dựa trên ví dụ.
Bắt Đầu: Kiểm Thử Hypothesis Đầu Tiên Của Bạn
Hãy bắt tay vào thực hành. Cách tốt nhất để hiểu Hypothesis là xem nó hoạt động.
Cài Đặt
Đầu tiên, bạn cần cài đặt Hypothesis và trình chạy kiểm thử bạn chọn (chúng tôi sẽ sử dụng pytest
). Nó đơn giản như:
pip install pytest hypothesis
Một Ví Dụ Đơn Giản: Hàm Giá Trị Tuyệt Đối
Hãy xem xét một hàm đơn giản được cho là tính giá trị tuyệt đối của một số. Một triển khai hơi lỗi có thể trông như thế này:
# in a file named `my_math.py` def custom_abs(x): """A custom implementation of the absolute value function.""" if x < 0: return -x return x
Bây giờ, hãy viết một tệp kiểm tra, test_my_math.py
. Đầu tiên, cách tiếp cận pytest
truyền thống:
# test_my_math.py (Example-based) def test_abs_positive(): assert custom_abs(5) == 5 def test_abs_negative(): assert custom_abs(-5) == 5 def test_abs_zero(): assert custom_abs(0) == 0
Các bài kiểm tra này vượt qua. Hàm của chúng ta trông chính xác dựa trên các ví dụ này. Nhưng bây giờ, hãy viết một kiểm thử dựa trên thuộc tính với Hypothesis. Thuộc tính cốt lõi của hàm giá trị tuyệt đối là gì? Kết quả không bao giờ được âm.
# test_my_math.py (Property-based with Hypothesis) from hypothesis import given from hypothesis import strategies as st from my_math import custom_abs @given(st.integers()) def test_abs_property_is_non_negative(x): """Property: The absolute value of any integer is always >= 0.""" assert custom_abs(x) >= 0
Hãy chia nhỏ điều này:
from hypothesis import given, strategies as st
: Chúng ta nhập các thành phần cần thiết.given
là một decorator biến một hàm kiểm tra thông thường thành một kiểm tra dựa trên thuộc tính.strategies
là module nơi chúng ta tìm thấy các trình tạo dữ liệu của mình.@given(st.integers())
: Đây là cốt lõi của kiểm tra. Decorator@given
yêu cầu Hypothesis chạy hàm kiểm tra này nhiều lần. Đối với mỗi lần chạy, nó sẽ tạo một giá trị bằng cách sử dụng chiến lược được cung cấp,st.integers()
và chuyển nó làm đối sốx
cho hàm kiểm tra của chúng ta.assert custom_abs(x) >= 0
: Đây là thuộc tính của chúng ta. Chúng ta khẳng định rằng đối với bất kỳ số nguyênx
nào mà Hypothesis nghĩ ra, kết quả của hàm của chúng ta phải lớn hơn hoặc bằng không.
Khi bạn chạy điều này với pytest
, nó có thể sẽ vượt qua cho nhiều giá trị. Hypothesis sẽ thử 0, -1, 1, các số dương lớn, các số âm lớn và hơn thế nữa. Hàm đơn giản của chúng ta xử lý tất cả những điều này một cách chính xác. Bây giờ, hãy thử một chiến lược khác để xem chúng ta có thể tìm thấy điểm yếu không.
# Let's test with floating point numbers @given(st.floats()) def test_abs_floats_property(x): assert custom_abs(x) >= 0
Nếu bạn chạy điều này, Hypothesis sẽ nhanh chóng tìm thấy một trường hợp thất bại!
Falsifying example: test_abs_floats_property(x=nan) ... assert custom_abs(nan) >= 0 AssertionError: assert nan >= 0
Hypothesis phát hiện ra rằng hàm của chúng ta, khi được cung cấp float('nan')
(Không phải là Số), trả về nan
. Khẳng định nan >= 0
là sai. Chúng ta vừa tìm thấy một lỗi tinh vi mà chúng ta có thể sẽ không nghĩ đến việc kiểm tra thủ công. Chúng ta có thể sửa hàm của mình để xử lý trường hợp này, có lẽ bằng cách đưa ra một ValueError
hoặc trả về một giá trị cụ thể.
Thậm chí tốt hơn, điều gì sẽ xảy ra nếu lỗi là với một số float rất cụ thể? Công cụ thu hẹp của Hypothesis sẽ lấy một số thất bại lớn, phức tạp và giảm nó xuống phiên bản đơn giản nhất có thể vẫn kích hoạt lỗi.
Sức Mạnh của Các Chiến Lược: Tạo Dữ Liệu Kiểm Tra Của Bạn
Các chiến lược là trái tim của Hypothesis. Chúng là công thức để tạo dữ liệu. Thư viện bao gồm một loạt các chiến lược tích hợp và bạn có thể kết hợp và tùy chỉnh chúng để tạo ra hầu như bất kỳ cấu trúc dữ liệu nào bạn có thể tưởng tượng.
Các Chiến Lược Tích Hợp Phổ Biến
- Số:
st.integers(min_value=0, max_value=1000)
: Tạo số nguyên, tùy chọn trong một phạm vi cụ thể.st.floats(min_value=0.0, max_value=1.0, allow_nan=False, allow_infinity=False)
: Tạo số float, với khả năng kiểm soát chi tiết các giá trị đặc biệt.st.fractions()
,st.decimals()
- Văn Bản:
st.text(min_size=1, max_size=50)
: Tạo chuỗi unicode có độ dài nhất định.st.text(alphabet='abcdef0123456789')
: Tạo chuỗi từ một bộ ký tự cụ thể (ví dụ: cho mã hex).st.characters()
: Tạo các ký tự riêng lẻ.
- Bộ Sưu Tập:
st.lists(st.integers(), min_size=1)
: Tạo danh sách trong đó mỗi phần tử là một số nguyên. Lưu ý cách chúng ta chuyển một chiến lược khác làm đối số! Điều này được gọi là thành phần.st.tuples(st.text(), st.booleans())
: Tạo bộ với cấu trúc cố định.st.sets(st.integers())
st.dictionaries(keys=st.text(), values=st.integers())
: Tạo từ điển với các loại khóa và giá trị được chỉ định.
- Thời Gian:
st.dates()
,st.times()
,st.datetimes()
,st.timedeltas()
. Chúng có thể được tạo nhận biết múi giờ.
- Linh Tinh:
st.booleans()
: TạoTrue
hoặcFalse
.st.just('constant_value')
: Luôn tạo cùng một giá trị duy nhất. Hữu ích để tạo các chiến lược phức tạp.st.one_of(st.integers(), st.text())
: Tạo một giá trị từ một trong các chiến lược được cung cấp.st.none()
: Chỉ tạoNone
.
Kết Hợp và Chuyển Đổi Các Chiến Lược
Sức mạnh thực sự của Hypothesis đến từ khả năng xây dựng các chiến lược phức tạp từ các chiến lược đơn giản hơn.
Sử Dụng .map()
Phương thức .map()
cho phép bạn lấy một giá trị từ một chiến lược và chuyển đổi nó thành một thứ khác. Điều này là hoàn hảo để tạo các đối tượng của các lớp tùy chỉnh của bạn.
# A simple data class from dataclasses import dataclass @dataclass class User: user_id: int username: str # A strategy to generate User objects user_strategy = st.builds( User, user_id=st.integers(min_value=1), username=st.text(min_size=3, alphabet='abcdefghijklmnopqrstuvwxyz') ) @given(user=user_strategy) def test_user_creation(user): assert isinstance(user, User) assert user.user_id > 0 assert user.username.isalpha()
Sử Dụng .filter()
và assume()
Đôi khi bạn cần từ chối một số giá trị được tạo. Ví dụ: bạn có thể cần một danh sách các số nguyên trong đó tổng không bằng không. Bạn có thể sử dụng .filter()
:
st.lists(st.integers()).filter(lambda x: sum(x) != 0)
Tuy nhiên, sử dụng .filter()
có thể không hiệu quả. Nếu điều kiện thường xuyên sai, Hypothesis có thể mất nhiều thời gian để cố gắng tạo một ví dụ hợp lệ. Một cách tiếp cận tốt hơn thường là sử dụng assume()
bên trong hàm kiểm tra của bạn:
from hypothesis import assume @given(st.lists(st.integers())) def test_something_with_non_zero_sum_list(numbers): assume(sum(numbers) != 0) # ... your test logic here ...
assume()
nói với Hypothesis: "Nếu điều kiện này không được đáp ứng, chỉ cần loại bỏ ví dụ này và thử một ví dụ mới." Đó là một cách trực tiếp hơn và thường hiệu quả hơn để giới hạn dữ liệu kiểm tra của bạn.
Sử Dụng st.composite()
Đối với việc tạo dữ liệu thực sự phức tạp, trong đó một giá trị được tạo phụ thuộc vào một giá trị khác, st.composite()
là công cụ bạn cần. Nó cho phép bạn viết một hàm lấy một hàm draw
đặc biệt làm đối số, hàm này bạn có thể sử dụng để kéo các giá trị từ các chiến lược khác từng bước.
Một ví dụ cổ điển là tạo một danh sách và một chỉ mục hợp lệ vào danh sách đó.
@st.composite def list_and_index(draw): # First, draw a non-empty list my_list = draw(st.lists(st.integers(), min_size=1)) # Then, draw an index that is guaranteed to be valid for that list index = draw(st.integers(min_value=0, max_value=len(my_list) - 1)) return (my_list, index) @given(data=list_and_index()) def test_list_access(data): my_list, index = data # This access is guaranteed to be safe because of how we built the strategy element = my_list[index] assert element is not None # A simple assertion
Hypothesis Trong Hành Động: Các Tình Huống Thực Tế
Hãy áp dụng các khái niệm này cho các vấn đề thực tế hơn mà các nhà phát triển phần mềm phải đối mặt hàng ngày.
Tình Huống 1: Kiểm Tra Hàm Tuần Tự Hóa Dữ Liệu
Hãy tưởng tượng một hàm tuần tự hóa một hồ sơ người dùng (một từ điển) thành một chuỗi an toàn với URL và một hàm khác giải tuần tự hóa nó. Một thuộc tính quan trọng là quá trình này phải hoàn toàn có thể đảo ngược.
import json import base64 def serialize_profile(data: dict) -> str: """Serializes a dictionary to a URL-safe base64 string.""" json_string = json.dumps(data) return base64.urlsafe_b64encode(json_string.encode('utf-8')).decode('utf-8') def deserialize_profile(encoded_str: str) -> dict: """Deserializes a string back into a dictionary.""" json_string = base64.urlsafe_b64decode(encoded_str.encode('utf-8')).decode('utf-8') return json.loads(json_string) # Now for the test # We need a strategy that generates JSON-compatible dictionaries json_dictionaries = st.dictionaries( keys=st.text(), values=st.recursive(st.none() | st.booleans() | st.floats(allow_nan=False) | st.text(), lambda children: st.lists(children) | st.dictionaries(st.text(), children), max_leaves=10) ) @given(profile=json_dictionaries) def test_serialization_roundtrip(profile): """Property: Deserializing an encoded profile should return the original profile.""" encoded = serialize_profile(profile) decoded = deserialize_profile(encoded) assert profile == decoded
Kiểm tra đơn lẻ này sẽ tấn công các hàm của chúng ta với một loạt dữ liệu khổng lồ: từ điển trống, từ điển có danh sách lồng nhau, từ điển có ký tự unicode, từ điển có các khóa lạ và hơn thế nữa. Nó kỹ lưỡng hơn nhiều so với việc viết một vài ví dụ thủ công.
Tình Huống 2: Kiểm Tra Thuật Toán Sắp Xếp
Hãy xem lại ví dụ sắp xếp của chúng ta. Đây là cách bạn sẽ kiểm tra các thuộc tính chúng ta đã xác định trước đó.
from collections import Counter def my_buggy_sort(numbers): # Let's introduce a subtle bug: it drops duplicates return sorted(list(set(numbers))) @given(st.lists(st.integers())) def test_sorting_properties(numbers): sorted_list = my_buggy_sort(numbers) # Property 1: The output is sorted for i in range(len(sorted_list) - 1): assert sorted_list[i] <= sorted_list[i+1] # Property 2: The elements are the same (this will find the bug) assert Counter(numbers) == Counter(sorted_list) # Property 3: The function is idempotent assert my_buggy_sort(sorted_list) == sorted_list
Khi bạn chạy thử nghiệm này, Hypothesis sẽ nhanh chóng tìm thấy một ví dụ không thành công cho Thuộc tính 2, chẳng hạn như numbers=[0, 0]
. Hàm của chúng ta trả về [0]
và Counter([0, 0])
không bằng Counter([0])
. Công cụ thu nhỏ sẽ đảm bảo ví dụ không thành công đơn giản nhất có thể, làm cho nguyên nhân của lỗi trở nên rõ ràng ngay lập tức.
Tình Huống 3: Kiểm Thử Có Trạng Thái
Đối với các đối tượng có trạng thái nội bộ thay đổi theo thời gian (như kết nối cơ sở dữ liệu, giỏ hàng hoặc bộ nhớ cache), việc tìm lỗi có thể cực kỳ khó khăn. Một chuỗi thao tác cụ thể có thể được yêu cầu để kích hoạt lỗi. Hypothesis cung cấp `RuleBasedStateMachine` chính xác cho mục đích này.
Hãy tưởng tượng một API đơn giản cho một kho lưu trữ khóa-giá trị trong bộ nhớ:
class SimpleKeyValueStore: def __init__(self): self._data = {} def set(self, key, value): self._data[key] = value def get(self, key): return self._data.get(key) def delete(self, key): if key in self._data: del self._data[key] def size(self): return len(self._data)
Chúng ta có thể mô hình hóa hành vi của nó và kiểm tra nó bằng một máy trạng thái:
from hypothesis.stateful import RuleBasedStateMachine, rule, Bundle class KeyValueStoreMachine(RuleBasedStateMachine): def __init__(self): super().__init__() self.model = {} self.sut = SimpleKeyValueStore() # Bundle() is used to pass data between rules keys = Bundle('keys') @rule(target=keys, key=st.text(), value=st.integers()) def set_key(self, key, value): self.model[key] = value self.sut.set(key, value) return key @rule(key=keys) def delete_key(self, key): del self.model[key] self.sut.delete(key) @rule(key=st.text()) def get_key(self, key): model_val = self.model.get(key) sut_val = self.sut.get(key) assert model_val == sut_val @rule() def check_size(self): assert len(self.model) == self.sut.size() # To run the test, you simply subclass from the machine and unittest.TestCase # In pytest, you can simply assign the test to the machine class TestKeyValueStore = KeyValueStoreMachine.TestCase
Hypothesis bây giờ sẽ thực thi các chuỗi ngẫu nhiên các hoạt động `set_key`, `delete_key`, `get_key` và `check_size`, không ngừng cố gắng tìm một chuỗi gây ra một trong các khẳng định không thành công. Nó sẽ kiểm tra xem việc nhận một khóa đã xóa có hoạt động chính xác hay không, nếu kích thước nhất quán sau nhiều lần đặt và xóa và nhiều tình huống khác mà bạn có thể không nghĩ đến việc kiểm tra thủ công.
Các Phương Pháp Hay Nhất và Mẹo Nâng Cao
- Cơ Sở Dữ Liệu Ví Dụ: Hypothesis rất thông minh. Khi tìm thấy một lỗi, nó sẽ lưu ví dụ không thành công trong một thư mục cục bộ (
.hypothesis/
). Lần tới khi bạn chạy kiểm tra của mình, nó sẽ phát lại ví dụ không thành công đó trước tiên, cung cấp cho bạn phản hồi ngay lập tức rằng lỗi vẫn còn. Sau khi bạn sửa nó, ví dụ không còn được phát lại. - Kiểm Soát Thực Thi Kiểm Tra với
@settings
: Bạn có thể kiểm soát nhiều khía cạnh của quá trình chạy thử nghiệm bằng cách sử dụng decorator@settings
. Bạn có thể tăng số lượng ví dụ, đặt thời hạn cho thời gian một ví dụ có thể chạy (để bắt các vòng lặp vô hạn) và tắt một số kiểm tra sức khỏe.@settings(max_examples=500, deadline=1000) # Run 500 examples, 1-second deadline @given(...) ...
- Sao Chép Các Lỗi: Mỗi lần chạy Hypothesis in một giá trị seed (ví dụ:
@reproduce_failure('version', 'seed')
). Nếu một máy chủ CI tìm thấy một lỗi mà bạn không thể sao chép cục bộ, bạn có thể sử dụng decorator này với seed được cung cấp để buộc Hypothesis chạy chính xác cùng một chuỗi ví dụ. - Tích Hợp với CI/CD: Hypothesis là một lựa chọn hoàn hảo cho bất kỳ quy trình tích hợp liên tục nào. Khả năng tìm ra các lỗi khó hiểu trước khi chúng đến sản xuất khiến nó trở thành một mạng lưới an toàn vô giá.
Sự Thay Đổi Tư Duy: Tư Duy Về Các Thuộc Tính
Việc áp dụng Hypothesis không chỉ là học một thư viện mới; đó là nắm lấy một cách suy nghĩ mới về tính chính xác của mã của bạn. Thay vì hỏi, "Tôi nên kiểm tra những đầu vào nào?", bạn bắt đầu hỏi, "Những sự thật phổ quát về mã này là gì?"
Dưới đây là một số câu hỏi để hướng dẫn bạn khi cố gắng xác định các thuộc tính:
- Có một thao tác ngược lại không? (ví dụ: tuần tự hóa/giải tuần tự hóa, mã hóa/giải mã, nén/giải nén). Thuộc tính là thực hiện thao tác và thao tác ngược lại của nó sẽ tạo ra đầu vào ban đầu.
- Thao tác có lũy đẳng không? (ví dụ:
abs(abs(x)) == abs(x)
). Áp dụng hàm nhiều lần sẽ tạo ra kết quả tương tự như áp dụng nó một lần. - Có một cách khác, đơn giản hơn để tính toán cùng một kết quả không? Bạn có thể kiểm tra xem hàm phức tạp, được tối ưu hóa của bạn có tạo ra cùng một đầu ra như một phiên bản đơn giản, rõ ràng là chính xác hay không (ví dụ: kiểm tra loại ưa thích của bạn so với
sorted()
tích hợp của Python). - Điều gì sẽ luôn đúng về đầu ra? (ví dụ: đầu ra của một hàm `find_prime_factors` chỉ được chứa các số nguyên tố và tích của chúng phải bằng đầu vào).
- Trạng thái thay đổi như thế nào? (Đối với kiểm thử có trạng thái) Những bất biến nào phải được duy trì sau bất kỳ thao tác hợp lệ nào? (ví dụ: Số lượng mặt hàng trong giỏ hàng không bao giờ có thể âm).
Kết Luận: Một Cấp Độ Tự Tin Mới
Kiểm thử dựa trên thuộc tính với Hypothesis không thay thế kiểm thử dựa trên ví dụ. Bạn vẫn cần các kiểm thử cụ thể, được viết tay cho logic nghiệp vụ quan trọng và các yêu cầu được hiểu rõ (ví dụ: "Người dùng từ quốc gia X phải thấy giá Y").
Những gì Hypothesis cung cấp là một cách tự động, mạnh mẽ để khám phá hành vi của mã của bạn và bảo vệ chống lại các trường hợp đặc biệt không lường trước được. Nó hoạt động như một đối tác không mệt mỏi, tạo ra hàng nghìn thử nghiệm đa dạng và xảo quyệt hơn bất kỳ con người nào có thể viết một cách thực tế. Bằng cách xác định các thuộc tính cơ bản của mã của bạn, bạn tạo ra một đặc tả mạnh mẽ mà Hypothesis có thể kiểm tra, mang lại cho bạn một mức độ tự tin mới vào phần mềm của mình.
Lần tới khi bạn viết một hàm, hãy dành một chút thời gian để suy nghĩ vượt ra ngoài các ví dụ. Hãy tự hỏi mình, "Các quy tắc là gì? Điều gì phải luôn đúng?" Sau đó, hãy để Hypothesis thực hiện công việc khó khăn là cố gắng phá vỡ chúng. Bạn sẽ ngạc nhiên về những gì nó tìm thấy và mã của bạn sẽ tốt hơn vì nó.