一个用于编写内联测试的pytest插件。
项目描述
pytest-inline
pytest-inline是一个pytest插件,用于编写内联测试。
内联测试是一种新的测试粒度,它使得检查单个程序语句变得更容易。内联测试是一种语句,允许开发者提供任意输入和测试预言机来检查紧邻的前一个语句(该语句不是内联测试)。与通常放置在单独的test_*.py
文件中的单元测试不同,内联测试与实际的生产代码一起编写(因此更容易维护)。
目录
示例
此代码片段中的正则表达式(第7行)检查变量名是否匹配以冒号结尾且至少包含一个数字的模式。为我们编写的针对目标语句(第7行)的内联测试由三个部分组成
- 使用itest()构造函数声明
- 使用given()函数调用分配输入
- 使用check_*()函数调用指定测试预言机
from inline import itest
def get_assignment_map_from_checkpoint(tvars, init_c):
...
for var in tvars:
name = var.name
m = re.match("^(.*):\\d+$", name)
itest().given(name, "a:0").check_eq(m, "a")
if m is not None:
name = m.group(1)
...
安装
使用pip install pytest-inline
安装此插件。
使用
使用pytest .
运行工作目录中的所有内联测试。
使用pytest {filename}
运行Python文件中的所有内联测试。
API
内联测试的声明
- itest(test_name, parameterized, repeated, tag, disabled, timeout)
-
test_name是一个表示测试名称的字符串。默认值是文件名加上测试语句的行号。
-
parameterized是一个布尔值,表示测试是否是参数化的。默认值是false。
-
repeated是一个整数,表示测试重复的次数。默认值是1。
-
tag是一个表示测试标签的字符串。默认值是一个空字符串。
-
disabled是一个布尔值,表示测试是否被禁用。默认值是false。
-
timeout是一个浮点数,表示测试运行到超时所需的时间。默认值是-1.0。如果测试超时,它将抛出一个超时异常。
使用assume()调用提供任何假设
-
assume(condition): 检查条件是否成立,如果条件不成立,则测试不会运行。
如果添加了assume检查,它必须发生在任何given语句之前,并且只能添加一个。
以下是一个玩具示例。它仅仅说明了如何使用assume语句调用。可以在assume语句中使用更复杂的逻辑,例如检查给定的系统版本,并假设只有当假设的系统版本为真时才运行测试。
def FileHeader(self): dt = self.date_time dosdate = (dt[0] - 1980) << 9 | dt[1] << 5 | dt[2] itest().assume(2 < 4).given(dt, (1980, 1, 25, 17, 13, 14)).check_eq(dosdate, 57)
使用given()调用提供测试输入
-
given(variable, value): 将值分配给变量。
请注意,可以添加任意数量的given语句。以下是一个此功能的简单示例。此外,第一个given调用必须在一个itest()声明或一个assume()调用之前进行,如果添加了它。
def multiple_givens(a, c): b = a + c itest().given(a, 2).given(c, a + 1).check_true(b == 5)
使用check()调用指定测试预言机
-
check_eq(actual_value, expected_value): 检查实际值是否等于预期值。
-
check_neq(actual_value, expected_value): 检查实际值是否不等于预期值。
-
check_true(expr): 检查布尔表达式是否为真。
-
check_false(expr): 检查布尔表达式是否为假。
-
check_none(variable): 检查变量是否为none。
-
check_not_none(variable): 检查变量是否不为none。
-
check_same(actual_value, expected_value): 检查实际值和预期值是否引用同一对象。
-
check_not_same(actual_value, expected_value): 检查实际值和预期值是否引用不同的对象。
对于给定的内联测试调用,只能指定一个测试预言机。
性能
内联测试通常运行得很快,因为每个内联测试只检查一个语句。请注意,当不运行测试时,内联测试的行为就像空函数调用一样,将它们放入生产代码中的开销是可以忽略不计的。
我们评估了pytest-inline在一份数据集上的性能,该数据集包含87个内联测试,这些测试是为31个开源项目中的50个示例中的80条语句编写的。主要发现总结如下,更多详情请参阅[我们的论文][paper-url]。我们进行了3次实验
-
以独立模式运行内联测试。每个内联测试的平均耗时为0.147秒。大部分时间都花费在启动和解析文件上,如果有更多内联测试在文件中,这可以优化。因此,我们也对内联测试进行了10次、100次、1000次的复制,每次内联测试的平均时间分别降低到0.015秒、0.002秒、0.001秒。
-
与单元测试一起运行内联测试。与仅运行单元测试相比,平均开销仅为0.007倍。即使将内联测试复制1000次(使得内联测试的总数与单元测试的总数相匹配),平均开销也只有0.088倍。
-
与单元测试一起运行内联测试,但禁用pytest-inline。这是为了模拟内联测试在生产代码中的成本。开销可以忽略不计:当我们不复制内联测试时,得到的数据是-0.001倍(由于噪声),只有当复制内联测试1000次时,才增加到0.019倍。
所有内联测试的API在非测试模式下都表现为空函数调用,总是返回一个虚拟对象,例如,check_eq定义为:def check_eq(self, ...): return self
。根据我们的实验观察,这通常不会产生可忽略不计的开销,但请注意,每次遇到内联测试时都要支付成本,因此如果内联测试位于将被多次执行的代码部分(例如,循环)中,这可能会累积。
引用
标题:内联测试
作者:Yu Liu、Pengyu Nie、Owolabi Legunsen、Milos Gligoric
@inproceedings{LiuASE22InlineTests,
title = {Inline Tests},
author = {Yu Liu and Pengyu Nie and Owolabi Legunsen and Milos Gligoric},
pages = {1--13},
booktitle = {International Conference on Automated Software Engineering},
year = {2022},
}
作者:Yu Liu、Zachary Thurston、Alan Han、Pengyu Nie、Milos Gligoric、Owolabi Legunsen
@inproceedings{LiuICSE23PytestInline,
title = {pytest-inline: An Inline Testing Tool for Python},
author = {Yu Liu and Zachary Thurston and Alan Han and Pengyu Nie and Milos Gligoric and Owolabi Legunsen},
pages = {1--4},
booktitle = {International Conference on Software Engineering, DEMO},
year = {2023},
}
项目详情
下载文件
下载适用于您平台的文件。如果您不确定选择哪个,请了解更多关于安装包的信息。