使用参数化功能为Python软件包编写非常简洁的单元测试的指南。
第一部分将说明随附软件包的结构以及如何在您的系统上进行设置。 如果您只想查看PyTest中的参数化示例,则可以直接跳至第二部分"参数化测试"。
包装结构和设置
克隆的存储库将包含一个名为efficiency_dedup的包和一个包含测试用例的tests文件夹。
该软件包旨在在Python列表中执行有效的重复数据删除(删除重复值)。 它分两个步骤执行此操作-首先,对列表进行排序,然后使用一种重复数据删除算法对已排序的列表进行重复数据删除。 请注意,此软件包仅用于说明; 因此,许多代码在功能上是多余的。 这是包的结构:
efficient_dedup
|
| - efficient_deduplication
| - deduplication_algorithms
| - sorting_algorithms
|
| - tests
|
| - test_efficient_deduplication
它基于策略设计模式。 高效重复数据删除模块中定义的EfficientDeduplicator类是上下文:
from .deduplication_algorithms import DeduplicationAlgorithmfrom .sorting_algorithms import SortingAlgorithmclass EfficientDeduplicator: def __init__(self, sorter: SortingAlgorithm, deduplicator: DeduplicationAlgorithm): self.__sorter = sorting_algorithm self.__deduplicator = deduplication_algorithm def deduplicate_efficiently(self, list_to_deduplicate): return self.__deduplicator.deduplicate(self.__sorter.sort(list_to_deduplicate))
要求每个deduplication_algorithms和sorting_algorithms模块都实施策略。
在sorting_algorithms模块中仅定义了两种策略行为,它们都封装在一个类SortingAlgorithm中。 您可以通过将反向参数设置为True或False来选择所需的特定行为:
class SortingAlgorithm: def __init__(self, reverse=False): self.__reverse = reverse def sort(self, list_to_sort): if self.__reverse: list_to_sort.sort(reverse=True) else: list_to_sort.sort() return list_to_sort
deduplication_algorithms模块包含四个不同的实现,每个实现封装在一个不同的类中。 第一个类DeduplicationAlgorithm充当其他三个实现的基类(这不是实际的代码,因为此处省略了函数的定义):
class DeduplicationAlgorithm: def deduplicate(self, list_with_duplicates): return class HashSetDeduplicationAlgorithm(DeduplicationAlgorithm): def deduplicate(self, list_with_duplicates): return class CustomComparisonDeduplicationAlgorithm(DeduplicationAlgorithm): def __init__(self, comparison_fun=lambda x, y: x == y): self.__comparison_fun = comparison_fun def deduplicate(self, list_with_duplicates): returnclass LookaheadCustomComparisonDeduplicationAlgorithm(DeduplicationAlgorithm): def __init__(self, lookahead_window_size=100, comparison_fun=lambda x, y: x == y): self.__lookahead_window_size = lookahead_window_size self.__comparison_fun = comparison_fun def deduplicate(self, list_with_duplicates): return
要运行测试用例,将cd放入软件包的根文件夹并运行以下命令:
> cd /efficient_dedup> python -m pytest
参数化测试
因此,在继续操作之前,让我们先进行总结。 我们有一个称为EfficientDeduplicator的上下文类,它需要两种策略:SortingAlgorithm和DeduplicationAlgorithm。 该上下文类仅具有一个功能,即deduplicate_效率地。
我们希望使用策略的所有可能组合以及一些不同的测试数据来高效地测试重复数据删除。 正如我们将看到的,如果使用参数化,则可以在一个测试用例中完成所有这些工作。
数据参数化
这是我们可以编写的最基本的测试用例:
def test_deduplicate_efficiently(self): sorter = SortingAlgorithm( reverse=False ) deduplicator = LookaheadCustomComparisonDeduplicationAlgorithm( lookahead_window_size=100, comparison_fun=lambda x, y: x == y ) efficient_deduplicator = EfficientDeduplicator(sorter, deduplicator) list_with_duplicates = [1, 2, 2, 7, 4, 8, 9, 3, 5, 4, 6, 3, 1, 7, 3, 3] assert set(efficient_deduplicator.deduplicate_efficiently(list_with_duplicates)) == set(list_with_duplicates)
假设我们要使用几种可能的输入(即,变量list_with_duplicates的几种不同值)来执行此测试。 首先,我们在测试函数中使list_with_duplicates为参数。 然后,我们通过@ pytest.marker.parametrize装饰器提供该参数的值列表。
@pytest.mark.parametrize('list_with_duplicates', [ [1, 2, 2, 7, 4, 8, 9, 3, 5, 4, 6, 3, 1, 7, 3, 3], [1, 2, 3, 5, 7, 9, 4, 7, 8], [1, 1, 1, 1, 1, 1, 1, 1, 1] ]) def test_deduplicate_efficiently_w_data_parametrization(self, list_with_duplicates): sorter = SortingAlgorithm() deduplicator = DeduplicationAlgorithm() efficient_deduplicator = EfficientDeduplicator(sorter, deduplicator) assert set(efficient_deduplicator.deduplicate_efficiently(list_with_duplicates)) == set(list_with_duplicates)
这是在Pytest中进行参数化的一般过程:
· 您可以在测试函数的参数列表中指定要参数化的变量
· 然后,使用@ pytest.marker.parametrize装饰器提供变量值的列表。
类的参数化
现在,假设我们想对DeduplicationAlgorithm的所有可能的不同实现运行基本测试,但要保持数据不变。 这可以通过Pytest参数化,Python的inspect模块以及类在Python中是一等公民的巧妙组合来实现(这意味着它们可以作为函数的参数传递)。
要获取deduplication_algorithms模块中所有类的列表,我们可以导入该模块,然后运行以下代码:
print([ getattr(deduplication_algorithms, name) for name, obj in inspect.getmembers(deduplication_algorithms) if inspect.isclass(obj)])
这为我们提供了模块中的类的列表:
[, ,,]
然后我们可以将其传递给@ pytest.marker.parametrize装饰器:
@pytest.mark.parametrize('deduplication_algorithm_class', [ getattr(deduplication_algorithms, name) for name, obj in inspect.getmembers(deduplication_algorithms) if inspect.isclass(obj) ])def test_deduplicate_efficiently_w_class_parametrization(self, deduplication_algorithm_class): sorter = SortingAlgorithm() deduplicator = deduplication_algorithm_class() efficient_deduplicator = EfficientDeduplicator(sorter, deduplicator) list_with_duplicates = [1, 2, 2, 7, 4, 8, 9, 3, 5, 4, 6, 3, 1, 7, 3, 3] assert set(efficient_deduplicator.deduplicate_efficiently(list_with_duplicates)) == set(list_with_duplicates)
等等。 您已经在Pytest中实现了类参数化!
结合数据和类参数化
Pytest的一个很棒的功能是,如果在一个测试函数上堆叠许多@ pytest.marker.parametrize装饰器,它将自动为所有装饰器(如固定产品)上的所有可能组合运行测试。 最后,我们将使用此功能在单个测试用例中结合上述两个参数; 实际上,我们还将对SortingAlgorithm的reverse属性进行参数化:
@pytest.mark.parametrize('list_with_duplicates', [ [1, 2, 2, 7, 4, 8, 9, 3, 5, 4, 6, 3, 1, 7, 3, 3], [1, 2, 3, 5, 7, 9, 4, 7, 8], [1, 1, 1, 1, 1, 1, 1, 1, 1] ]) @pytest.mark.parametrize('sorting_algorithm_reverse_option', [ True, False ]) @pytest.mark.parametrize('deduplication_algorithm_class', [ getattr(deduplication_algorithms, name) for name, obj in inspect.getmembers(deduplication_algorithms) if inspect.isclass(obj) ]) def test_deduplicate_efficiently_w_data_and_class_parametrization(self, list_with_duplicates, sorting_algorithm_reverse_option, deduplication_algorithm_class): sorter = SortingAlgorithm(reverse=sorting_algorithm_reverse_option) deduplicator = deduplication_algorithm_class() efficient_deduplicator = EfficientDeduplicator(sorter, deduplicator) assert set(efficient_deduplicator.deduplicate_efficiently(list_with_duplicates)) == set(list_with_duplicates)
这是您要克隆的软件包中测试用例的最终形式。 如果您详细运行Pytest:
> python -m pytest -v
您将观察到这样的输出:
tests/test_efficient_deduplication.py::TestEfficientDeduplicators::test_deduplicate_efficiently[DeduplicationAlgorithm-True-list_with_duplicates0]
PASSED [33%]
上面输出中的第二行列出了测试用例中使用的参数的确切组合。
结论
使用本文中介绍的技术,将来可以编写更简洁有效的Python测试用例!
要克隆本文随附的源代码,请私信译者。
(本文翻译自Rishabh Malviya的文章《PyTest: An Introduction to Parametrization》,参考:https://levelup.gitconnected.com/pytest-an-introduction-to-parametrization-2b27bebb6d4b)