用 Hypothesis 快速测试你的 Python 代码
介绍
无论你使用哪种编程语言或框架,测试都非常重要。Hypothesis
是 Python 的一个高级测试库。它允许编写测试用例时参数化,然后生成使测试失败的简单易懂的测试数据。可以用更少的工作在代码中发现更多的bug。该测试库覆盖了大多数情况,并且确实可以帮助你查找代码中的错误。
这篇文章为展示了如何使用Hypothesis
在Python中进行测试,并提供了一些示例。
我们如何区分测试?
在我们开始进行基于属性的测试之前,我们需要知道测试的一般区别。有不同的分组测试方法,两种最常见的方法基于测试方法和测试级别。让我们从大多数人已经听说的测试级别开始。本质上,存在四个测试级别(尽管人们可能也知道或定义其他级别):
单元测试 集成测试 系统测试 端到端测试
不同测试级别侧重专注于不同的事物。单元测试侧重于软件的特定部分或功能。这可以是单个功能或功能的一部分。相反,集成测试侧重于通过软件组件的接口进行协作。系统测试甚至更进一步,可以测试整个系统。
现在,我们将看看存在的各种各样的测试方法。
最常见和已知的是静态和动态测试。所谓静态测试(static testing)就是不实际运行被测软件,而只是静态地检查程序代码、界面或文档中可能存在的错误的过程。如果软件或其部分实际执行,我们称之为动态测试。编写单元测试和集成测试属于动态测试。
另一种常见的方法是盒式方法。基本上,它可以分为白盒测试和黑盒测试(以及灰盒测试作为两者的混合)。白盒测试可验证程序的内部结构或工作情况。黑盒测试与之相反,在黑盒测试中,应用程序被视为黑盒,并且对其交互进行测试。这意味着在不了解内部实现的情况下测试功能。
什么是基于属性的测试?
现在,我们快速了解了如何区分测试,您可能会问:什么是基于属性的测试?
基于属性的测试技术( Property-based testing),是指编写对你的代码来说为真的逻辑语句(即“属性”),然后使用自动化工具来生成测试输入(一般来说,是指某种特定类型的随机生成输入数据),并观察程序接受该输入时属性是否保持不变。如果某个输入违反了某一条属性,则用户证明程序存在一处错误,并找到一个能够演示该错误的便捷示例。
使用Hypothesis
进行基于属性的测试
让我们举一个简单的例子。假设您有两个函数crement()
和decrement()
。一个示例实现可能如下所示:
# increment_decrement.py
def increment(number: int) -> int:
return number + 1
def decrement(number: int) -> int:
return number - 1
您可能会为两者编写单元测试代码,如下所示:
# test_increment_decrement_pytest.py
from increment_decrement import decrement
from increment_decrement import increment
def test_increment():
x = 5
expected = 6
actual = increment(x)
assert actual == expected
def test_decrement():
x = 5
expected = 4
actual = decrement(x)
assert actual == expected
注意:测试代码是使用pytest框架编写的。
当然,您可以编写更多的测试脚本来测试具有不同值的两个函数,甚至可以对测试进行参数化。但是,最后您将使用预定义的值来测试这两个功能。
使用基于属性的测试库(例如Hypothesis )编写测试是不同的。在这里,您可以指定要测试的类型以及软件的工作方式或行为方式。然后该库根据指定的类型生成随机值来进行实际测试功能。
让我们看看如何使用Hypothesis
来测试我们的两个功能。
# test_increment_decrement_hypothesis.py
from hypothesis import given
import hypothesis.strategies as st
from increment_decrement import decrement
from increment_decrement import increment
@given(st.integers())
def test_increment(x):
expected = x + 1
actual = increment(x)
assert actual == expected
@given(st.integers())
def test_decrement(x):
expected = x - 1
actual = decrement(x)
assert actual == expected
如您所见,这两个测试脚本都有一个参数x
。x
的值是由Hypothesis
使用integers()
方法生成的。Hypothesis
提供了各种方法。本质上,这些方法对应于内置类型或其他结构,并生成与给定类型匹配的随机数据。
听起来不错,不是吗?但是,如果我们想测试具有特定值的函数以确保它也可以使用该值怎么办?Hypothesis
提供了一个@example()
装饰器,您可以在其中定义一个值,即使该值不属于随机生成的测试数据集,也可以将该值传递给相应的函数。
让我们举个简单的例子:
# div.py
def div(dividend: int, divisor: int) -> int:
return dividend // divisor
我们定义了一个函数div()
,该函数接受一个除数和一个被除数并返回两者的商。请注意,这两个参数都是整型数据,因此结果也应该是整型数据,我们使用Python的//
运算符执行整数除法。
为了测试div()
函数,我们创建了一个新的测试文件test_div.py
并编写了一个名为test_div()
的测试脚本。
# test_div.py
from hypothesis import example
from hypothesis import given
import hypothesis.strategies as st
from div import div
@given(dividend=st.integers(), divisor=st.integers())
def test_div(dividend, divisor):
if divisor == 0:
expected = -1
else:
expected = dividend // divisor
actual = div(dividend, divisor)
assert actual == expected
同样,我们使用Hypothesis
的integers()
方法生成除数和被除数的值。我们编写的测试脚本可能通过也可能不会通过,具体取决于执行时Hypothesis
产生的值。为了确保始终将值0
传递给div()
函数,我们将@example(1,0)
添加到test_div()
函数。因此,即使div()
不在随机生成的数据集中,也至少会用除数的值0
调用一次。
如果我们按原样运行测试脚本,则test_div()
将始终失败。因此,让我们修改div()
函数来处理这种情况并使测试通过:
# div.py
def div(dividend: int, divisor: int) -> int:
if divisor == 0:
return -1
return dividend // divisor
概要
本文主要讲了什么是基于属性的测试以及为什么有用。此外,您快速浏览了Hypothesis
库,该库使您可以编写基于属性的测试并与pytest测试一起执行。
恋习Python 关注恋习Python,Python都好练 好文章,我在看❤️