祝大家有美好的一天!
从我们的桌子到您的桌子。也就是说,从我们的课程“ Python Developer”开始,尽管新年快到了,但我们为您准备了有关Python中各种测试方法的有趣翻译。
本指南适用于那些已经编写了很酷的Python应用程序但尚未编写的应用程序。
他们测试。
使用Python进行测试是一个广泛的话题,包含很多细节,但是不必使事情复杂化。 通过几个简单的步骤,您可以为应用程序创建简单的测试,并根据这些测试逐渐增加复杂性。
在本指南中,您将学习如何创建基本测试,运行它并在用户进行操作之前查找所有错误! 您将了解用于编写和运行测试,检查应用程序性能甚至查看安全问题的可用工具。
代码测试您可以通过多种方式测试代码。 在本指南中,您将学习从最简单到最高级的方法。
自动化与 手动测试
好消息! 您很可能已经完成了测试,但尚未意识到这一点。 还记得您第一次启动该应用程序并使用它的方式吗? 您是否测试过功能并进行了实验? 此过程称为探索性测试,它是手动测试的一种形式。
研究测试-在没有计划的情况下完成的测试。 在研究测试期间,您将研究应用程序。
要创建完整的手动测试列表,只需列出应用程序的所有功能,所需的各种输入以及预期的结果即可。 现在,每次更改代码中的某些内容时,都需要重新检查此列表中的每个元素。
听起来很暗淡,对不对?
因此,需要自动测试。 自动测试-使用脚本而不是人工执行测试计划(应用程序中需要测试的部分,测试顺序和预期结果)。 Python已经有了一套工具和库来帮助您为应用程序创建自动化测试。 让我们在教程中查看这些工具和库。
VS单元测试。 整合测试测试的世界充满了术语,现在,了解手动测试和自动测试之间的区别,我们将更加深入。
想想如何测试汽车的前灯? 您打开大灯(将其称为测试步骤),自己下车,或请朋友检查大灯是否点亮(这是一个测试建议)。 测试多个组件称为集成测试。
考虑所有应该正常工作的事物,以便简单的任务可以产生正确的结果。 这些组件类似于您的应用程序的各个部分:您编写的所有这些类,函数,模块。
当集成测试没有给出正确的结果时,就会出现集成测试的主要困难。 无法评估问题,无法隔离系统的损坏部分。 如果前灯未点亮,则灯泡可能会损坏。 还是电池电量不足? 还是问题出在发电机上? 甚至是机器计算机崩溃?
现代汽车本身会通知您灯泡损坏。 这是使用单元测试确定的。
单元测试(unit test)是一种小型测试,用于检查单个组件的正确操作。 单元测试有助于隔离故障并更快地进行修复。
我们讨论了两种类型的测试:
- 集成测试,用于检查系统组件及其相互之间的交互;
- 单元测试,用于测试应用程序的单个组件。
- 您可以在Python中创建两个测试。 要为内置的sum()函数编写测试,您需要将sum()的输出与已知值进行比较。
例如,通过这种方式,您可以检查数字(1、2、3)的总和是否为6:
>>> assert sum([1, 2, 3]) == 6, "Should be 6"
值是正确的,因此什么也不会输出到REPL。 如果
sum()
的结果不正确,则会
AssertionError
并显示消息“应该为6”。 再次检查statement语句,但是现在使用无效值来获取
AssertionError
:
>>> assert sum([1, 1, 1]) == 6, "Should be 6" Traceback (most recent call last): File "<stdin>", line 1, in <module> AssertionError: Should be 6
在REPL中,由于
sum()
值不是6,因此您会看到
AssertionError
。
代替REPL,将其放在一个名为
test_sum.py
的新Python文件中,然后再次运行它:
def test_sum(): assert sum([1, 2, 3]) == 6, "Should be 6" if __name__ == "__main__": test_sum() print("Everything passed")
现在,您有一个书面的测试用例(测试用例),语句和入口点(命令行)。 现在可以在命令行上完成:
$ python test_sum.py Everything passed
您会看到成功的结果,“一切都过去了”。
Python中的
sum()
接受任何iterable作为第一个参数。 您已检查列表。 让我们尝试测试元组。 使用以下代码创建一个名为
test_sum_2.py
的新文件:
def test_sum(): assert sum([1, 2, 3]) == 6, "Should be 6" def test_sum_tuple(): assert sum((1, 2, 2)) == 6, "Should be 6" if __name__ == "__main__": test_sum() test_sum_tuple() print("Everything passed")
test_sum_2.py
,脚本将
test_sum_2.py
错误,因为s
um() (1, 2, 2)
test_sum_2.py
um() (1, 2, 2)
应该为5,而不是6。结果,脚本给出了一条错误消息,一行代码和一个回溯:
$ python test_sum_2.py Traceback (most recent call last): File "test_sum_2.py", line 9, in <module> test_sum_tuple() File "test_sum_2.py", line 5, in test_sum_tuple assert sum((1, 2, 2)) == 6, "Should be 6" AssertionError: Should be 6
您可以看到代码中的错误如何导致控制台中的错误,并提供有关错误发生的位置以及预期结果的信息。
这样的测试适合于简单的验证,但是如果错误多于一次,该怎么办? 测试选手来营救。 Test Executor是一个特殊的应用程序,旨在执行测试,验证输出数据并提供调试和诊断测试及应用程序的工具。
选择测试执行器有许多可用于Python的测试运行器。 例如,unittest内置在Python标准库中。 在本指南中,我们将使用测试用例和单元测试执行器。 单元测试的操作原理很容易适应其他框架。 我们列出了最受欢迎的测试执行者:
选择符合您要求和经验的测试承包商很重要。
单元测试自2.1版以来,unittest已集成到Python标准库中。 您可能会在商业Python应用程序和开源项目中遇到它。
Unittest有一个测试框架和测试运行程序。 在编写和运行测试时,您需要遵循一些重要的要求。
单元测试要求:
- 将测试作为方法放在类中;
- 使用特殊的批准方法。 TestCase类,而不是通常的内置断言表达式
要将先前编写的示例转换为单元测试用例,您必须:
- 从标准库导入单元测试;
- 创建一个名为
TestSum
的类,它将继承TestCase
类。 - 通过将
self
作为第一个参数,将测试函数转换为方法; - 通过在
TestCase
类中添加使用self.assertEqual()
方法来修改语句; - 在命令行上更改入口点以调用
unittest.main()
。
按照以下步骤,使用以下代码创建一个新的test_sum_unittest.py文件:
import unittest class TestSum(unittest.TestCase): def test_sum(self): self.assertEqual(sum([1, 2, 3]), 6, "Should be 6") def test_sum_tuple(self): self.assertEqual(sum((1, 2, 2)), 6, "Should be 6") if __name__ == '__main__': unittest.main()
通过在命令行上执行此操作,您将获得一个成功的完成(由表示。)和一个不成功的(由F表示):
$ python test_sum_unittest.py .F ====================================================================== FAIL: test_sum_tuple (__main__.TestSum) ---------------------------------------------------------------------- Traceback (most recent call last): File "test_sum_unittest.py", line 9, in test_sum_tuple self.assertEqual(sum((1, 2, 2)), 6, "Should be 6") AssertionError: Should be 6 ---------------------------------------------------------------------- Ran 2 tests in 0.001s FAILED (failures=1)
因此,您使用unittest测试运行程序执行了两个测试。
注意:如果要为Python 2和3编写测试用例,请小心。 在Python 2.7及更低版本中,unittest称为unittest2。从unittest导入时,您将在Python 2和Python 3中获得具有不同功能的不同版本。要了解有关unittest的更多信息,请阅读
unittest文档 。
鼻子随着时间的流逝,在为一个应用程序编写了数百甚至数千个测试之后,理解和使用单元测试输出数据变得越来越困难。
鼻子与用unittest框架编写的所有测试兼容,并且可以替换其测试执行程序。 作为一个开放源代码应用程序,nose的开发开始放缓,并创建了nose2。 如果您是从头开始的,建议使用准确的nose2。
要开始使用nas2,您需要从PyPl安装它并在命令行上运行它。 如果在名称中包含
test*.py
以及从当前目录中继承自unittest.TestCase的所有测试用例,nas2将尝试查找所有带有
test*.py
测试脚本:
$ pip install nose2 $ python -m nose2 .F ====================================================================== FAIL: test_sum_tuple (__main__.TestSum) ---------------------------------------------------------------------- Traceback (most recent call last): File "test_sum_unittest.py", line 9, in test_sum_tuple self.assertEqual(sum((1, 2, 2)), 6, "Should be 6") AssertionError: Should be 6 ---------------------------------------------------------------------- Ran 2 tests in 0.001s FAILED (failures=1)
这是从nas2测试
test_sum_unittest.py
在
test_sum_unittest.py
创建的测试的方式。 nase2提供了许多命令行标志来过滤可执行的测试。 有关更多信息,请参阅
鼻子2文档 。
pytestpytest支持unittest测试用例。 但是pytest的真正优势在于它的测试用例。 pytest测试用例是Python文件中一系列函数,名称开头为test_。
其中还有其他有用的功能:
- 支持内置的断言表达式,而不是使用特殊的self.assert *()方法;
- 支持过滤测试用例;
- 从上次失败的测试中重新启动的能力;
- 由数百个扩展功能的插件组成的生态系统。
pytest的TestSum测试用例示例如下所示:
def test_sum(): assert sum([1, 2, 3]) == 6, "Should be 6" def test_sum_tuple(): assert sum((1, 2, 2)) == 6, "Should be 6"
使用类和命令行入口点摆脱了TestCase。
可以在
Pytest文档站点上找到更多信息。
编写第一个测试结合我们已经学习的所有内容,而不是内置的
sum()
函数,我们测试具有相同要求的简单实现。
为项目创建一个新文件夹,在其中创建一个名为my_sum的新文件夹。 在my_sum内,创建一个名为
_init_.py
的空文件。 该文件的存在意味着my_sum文件夹可以作为模块从父目录中导入。
文件夹结构如下所示:
project/
│
└── my_sum/
└── __init__.py
打开
my_sum/__init__.py
并创建一个名为
sum()
的新函数,该函数接受
my_sum/__init__.py
输入(列表,元组,集合)并添加值。
def sum(arg): total = 0 for val in arg: total += val return total
本示例创建一个名为
total
的变量,对
arg
所有值进行迭代并添加到
total
。 然后,在完成迭代后,将返回结果。
在哪里编写测试您可以通过创建一个包含第一个测试用例的
test.py
文件来开始编写测试。 为了进行测试,该文件应该能够导入您的应用程序,因此将
test.py
放在软件包上方的文件夹中。 目录树将如下所示:
project/
│
├── my_sum/
│ └── __init__.py
|
└── test.py
您会注意到,随着添加新测试,文件变得更加繁琐且难以维护,因此我们建议创建
tests/
文件夹并将测试拆分为多个文件。 确保所有文件的名称都以
test_
,以便测试运行
test_
理解Python文件包含需要运行的测试。 在大型项目中,根据测试的目的或用途将测试分为几个目录。
注意:什么是单个脚本?
您可以使用内置的__import__()
函数导入任何脚本属性:类,函数或变量。 而不是from my_sum import sum
写入以下内容: target = __import__("my_sum.py") sum = target.sum
使用__import__()
不必将项目文件夹转换为包,并且可以指定文件名。 如果文件名与标准软件包库的名称冲突,这将很有用。 例如,如果math.py
与math模块冲突。如何构造一个简单的测试在编写测试之前,您需要解决一些问题:
- 您要测试什么?
- 您正在编写单元测试还是集成测试?
您目前正在测试
sum()
。 您可以为此测试不同的行为,例如:
- 是否可以汇总整数列表?
- 是否可以汇总一个元组或集合?
- 我可以总结一下浮点数列表吗?
- 如果您给输入一个错误的值:单个整数或字符串,会发生什么?
- 如果其中一个值为负,会发生什么?
测试的最简单方法是整数列表。 使用以下代码创建一个
test.py
文件:
import unittest from my_sum import sum class TestSum(unittest.TestCase): def test_list_int(self): """ Test that it can sum a list of integers """ data = [1, 2, 3] result = sum(data) self.assertEqual(result, 6) if __name__ == '__main__': unittest.main()
此示例中的代码:
- 从您创建的包
my_sum()
中导入sum()
; - 定义一个称为TestSum的新测试用例类,该类继承
unittest.TestCase
; - 定义用于测试整数列表的test
.test_list_int()
方法。 .test_list_int()
方法将执行以下操作
:
- 声明一个带有值列表
(1, 2, 3)
的data
变量; my_sum.sum(data)
值my_sum.sum(data)
变量result
;- 使用
unittest.TestCase
类的.assertEqual()
方法确定结果的值为6。
- 定义启动unittest
.main()
测试运行程序的命令行入口点。
如果您不知道什么是self或如何定义
.assertEqual()
,则可以使用
Python 3 Object-Oriented Programming刷新对
面向对象编程的知识 。
如何写陈述编写测试的最后一步是验证输出是否与已知值匹配。 这称为断言。 有一些用于编写语句的一般准则:
- 验证测试是否可重复,并运行几次以确保每次给出的结果相同;
- 检查并确认适用于您输入的结果-确认结果确实是
sum()
示例中值的sum()
。
单元测试有许多方法来确认变量的值,类型和存在。 以下是一些最常用的方法:
方法 | 当量 |
---|
.assertEqual(a,b) | a == b |
.assertTrue(x) | 布尔(x)为真 |
.assertFalse(x) | bool(x)为False |
.assertIs(a,b) | a是b |
.assertIsNone(x) | x为无 |
.assertIn(a,b) | a in b |
.assertIsInstance(a,b) | 实例(a,b) |
.assertIs()
.assertIsNone()
.assertIn()
和
.assertIsInstance()
具有相反的方法,称为
.assertIsNot()
等等。
副作用编写测试比仅查看函数的返回值难。 通常,代码执行会更改环境的其他部分:类属性,文件系统文件,数据库中的值。 这是测试的重要组成部分,称为副作用。 在将其包括在索赔清单中之前,请确定是否要测试副作用。
如果您发现要测试的代码块中有很多副作用,则您违反了“
唯一责任原则” 。 违反唯一责任原则意味着一段代码要做太多的事情,需要重构。 遵循唯一责任的原则是一种设计代码的好方法,为此,编写简单,可重复的单元测试并最终创建可靠的应用程序将不困难。
首次测试启动您创建了第一个测试,现在您需要尝试运行它。 很明显,它将通过,但是在创建更复杂的测试之前,您需要确保即使这样的测试也成功。
运行测试执行器测试执行程序-运行测试代码,验证断言并在控制台中显示测试结果的Python应用程序。 在test.py的末尾添加以下代码:
if __name__ == '__main__': unittest.main()
这是命令行入口点。 如果通过在命令行上运行python
test.py
来运行此脚本,它将调用
unittest.main()
。 这将
unittest.TestCase
检测此文件中所有继承自
unittest.TestCase
类来启动测试运行程序。
这是运行单元测试测试运行器的许多方法之一。 如果只有一个名为
test.py
测试文件,则调用python test.py是入门的好方法。
另一种方法是使用unittest命令行。 让我们尝试:
$ python -m unittest test
这将通过命令行执行相同的测试模块(称为
test
)。 您可以添加其他参数来更改输出。 其中之一是-v表示详细。 让我们尝试以下方法:
$ python -m unittest -v test test_list_int (test.TestSum) ... ok ---------------------------------------------------------------------- Ran 1 tests in 0.000s
我们从test.py运行了一个测试,并将结果输出到控制台。 详细模式列出了执行的测试的名称以及每个测试的结果。
除了提供包含测试的模块的名称之外,您还可以使用以下方法请求自动发现:
$ python -m unittest discover
该命令将在当前目录中查找名称为test
test*.py
文件以进行测试。
如果您有多个测试文件,并且遵循
test*.py
命名模式,则可以使用-s标志和文件夹名称来传递目录名称。
$ python -m unittest discover -s tests
unittest将在一个测试计划中运行所有测试并产生结果。
最后,如果源代码不在根目录中,而是在子目录中(例如,在名为src /的文件夹中),则可以使用-t标志告诉unittest在哪里运行测试以正确导入模块:
$ python -m unittest discover -s tests -t src
unittest将在
test*.py
内部的
src/
目录中找到所有
test*.py
文件,然后执行它们。
了解测试结果
这是一个非常简单的示例,说明一切正常,因此,让我们尝试了解失败的测试的输出。
sum()
必须接受其他数字类型的列表,例如小数。
在
test.py
代码的开头
test.py
添加一个表达式以从标准库的
fractions
模块导入
Fraction
类型。
from fractions import Fraction
现在添加一个带有语句的测试,期望值不正确。 在我们的例子中,我们期望¼,¼和the的总和等于1:
import unittest from my_sum import sum class TestSum(unittest.TestCase): def test_list_int(self): """ Test that it can sum a list of integers """ data = [1, 2, 3] result = sum(data) self.assertEqual(result, 6) def test_list_fraction(self): """ Test that it can sum a list of fractions """ data = [Fraction(1, 4), Fraction(1, 4), Fraction(2, 5)] result = sum(data) self.assertEqual(result, 1) if __name__ == '__main__': unittest.main()
如果使用python -m unittest test再次运行测试,请获取以下内容:
$ python -m unittest test F. ====================================================================== FAIL: test_list_fraction (test.TestSum) ---------------------------------------------------------------------- Traceback (most recent call last): File "test.py", line 21, in test_list_fraction self.assertEqual(result, 1) AssertionError: Fraction(9, 10) != 1 ---------------------------------------------------------------------- Ran 2 tests in 0.001s FAILED (failures=1)
在此输出中,您将看到以下内容:
- 第一行显示所有测试的结果:一个失败(F),一个通过(。);
- FAIL显示失败测试的一些详细信息:
- 测试方法的名称(
test_list_fraction
); - 测试模块(
test
)和测试用例( TestSum
); - 追溯字符串有错误;
- 带有预期结果(1)和实际结果(分数(9,10))的语句的详细信息
请记住,您可以使用
python -m unittest
的-v标志将其他信息添加到测试输出。
从PyCharm运行测试如果您使用的是PyCharm IDE,则可以按照以下步骤运行unittest或pytest:
- 在项目工具窗口中,选择测试目录。
- 在上下文菜单中,选择unittest run命令。 例如,“我的测试中的单元测试...”。
这将在测试窗口中执行unittest并在PyCharm中返回结果:

有关更多信息,请访问
PyCharm网站 。
从Visual Studio Code运行测试如果您使用Microsoft Visual Studio Code IDE,则Python插件已经内置了对unittest,nose和pytest的支持。
如果已安装,则可以通过使用Ctrl + Shift + P打开“命令面板”并编写“ Python测试”来配置测试配置。 您将看到选项列表:

选择“调试所有单元测试”,然后VSCode将发送请求以配置测试框架。 单击齿轮以选择测试运行器(unittest)和主目录(。)。
设置完成后,您将在屏幕底部看到测试状态,并且可以通过单击图标来快速访问测试日志并重新启动测试:

我们看到正在执行测试,但是其中一些失败了。
结束
在本文的下一部分中,我们将检查Django和Flask等框架的测试。
我们在这里等待您的问题和评论,并且与往常一样,您可以在
开放日前往斯坦尼斯拉夫。
第二部分