熟悉Python测试。 第一部分

祝大家有美好的一天!

从我们的桌子到您的桌子。也就是说,从我们的课程“ Python Developer”开始,尽管新年快到了,但我们为您准备了有关Python中各种测试方法的有趣翻译。

本指南适用于那些已经编写了很酷的Python应用程序但尚未编写的应用程序。
他们测试。

使用Python进行测试是一个广泛的话题,包含很多细节,但是不必使事情复杂化。 通过几个简单的步骤,您可以为应用程序创建简单的测试,并根据这些测试逐渐增加复杂性。

在本指南中,您将学习如何创建基本测试,运行它并在用户进行操作之前查找所有错误! 您将了解用于编写和运行测试,检查应用程序性能甚至查看安全问题的可用工具。



代码测试

您可以通过多种方式测试代码。 在本指南中,您将学习从最简单到最高级的方法。

自动化与 手动测试

好消息! 您很可能已经完成了测试,但尚未意识到这一点。 还记得您第一次启动该应用程序并使用它的方式吗? 您是否测试过功能并进行了实验? 此过程称为探索性测试,它是手动测试的一种形式。

研究测试-在没有计划的情况下完成的测试。 在研究测试期间,您将研究应用程序。

要创建完整的手动测试列表,只需列出应用程序的所有功能,所需的各种输入以及预期的结果即可。 现在,每次更改代码中的某些内容时,都需要重新检查此列表中的每个元素。

听起来很暗淡,对不对?

因此,需要自动测试。 自动测试-使用脚本而不是人工执行测试计划(应用程序中需要测试的部分,测试顺序和预期结果)。 Python已经有了一套工具和库来帮助您为应用程序创建自动化测试。 让我们在教程中查看这些工具和库。

VS单元测试。 整合测试

测试的世界充满了术语,现在,了解手动测试和自动测试之间的区别,我们将更加深入。

想想如何测试汽车的前灯? 您打开大灯(将其称为测试步骤),自己下车,或请朋友检查大灯是否点亮(这是一个测试建议)。 测试多个组件称为集成测试。

考虑所有应该正常工作的事物,以便简单的任务可以产生正确的结果。 这些组件类似于您的应用程序的各个部分:您编写的所有这些类,函数,模块。

当集成测试没有给出正确的结果时,就会出现集成测试的主要困难。 无法评估问题,无法隔离系统的损坏部分。 如果前灯未点亮,则灯泡可能会损坏。 还是电池电量不足? 还是问题出在发电机上? 甚至是机器计算机崩溃?

现代汽车本身会通知您灯泡损坏。 这是使用单元测试确定的。

单元测试(unit test)是一种小型测试,用于检查单个组件的正确操作。 单元测试有助于隔离故障并更快地进行修复。

我们讨论了两种类型的测试:

  1. 集成测试,用于检查系统组件及其相互之间的交互;
  2. 单元测试,用于测试应用程序的单个组件。
  3. 您可以在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;
  • pytest。

选择符合您要求和经验的测试承包商很重要。

单元测试

自2.1版以来,unittest已集成到Python标准库中。 您可能会在商业Python应用程序和开源项目中遇到它。
Unittest有一个测试框架和测试运行程序。 在编写和运行测试时,您需要遵循一些重要的要求。

单元测试要求:

  • 将测试作为方法放在类中;
  • 使用特殊的批准方法。 TestCase类,而不是通常的内置断言表达式


要将先前编写的示例转换为单元测试用例,您必须:

  1. 从标准库导入单元测试;
  2. 创建一个名为TestSum的类,它将继承TestCase类。
  3. 通过将self作为第一个参数,将测试函数转换为方法;
  4. 通过在TestCase类中添加使用self.assertEqual()方法来修改语句;
  5. 在命令行上更改入口点以调用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.pytest_sum_unittest.py创建的测试的方式。 nase2提供了许多命令行标志来过滤可执行的测试。 有关更多信息,请参阅鼻子2文档

pytest

pytest支持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模块冲突。

如何构造一个简单的测试

在编写测试之前,您需要解决一些问题:

  1. 您要测试什么?
  2. 您正在编写单元测试还是集成测试?

您目前正在测试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. 声明一个带有值列表(1, 2, 3)data变量;
  2. my_sum.sum(data)my_sum.sum(data)变量result
  3. 使用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显示失败测试的一些详细信息:

  1. 测试方法的名称( test_list_fraction );
  2. 测试模块( test )和测试用例( TestSum );
  3. 追溯字符串有错误;
  4. 带有预期结果(1)和实际结果(分数(9,10))的语句的详细信息

请记住,您可以使用python -m unittest的-v标志将其他信息添加到测试输出。

从PyCharm运行测试

如果您使用的是PyCharm IDE,则可以按照以下步骤运行unittest或pytest:

  1. 在项目工具窗口中,选择测试目录。
  2. 在上下文菜单中,选择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等框架的测试。

我们在这里等待您的问题和评论,并且与往常一样,您可以在开放日前往斯坦尼斯拉夫。

第二部分

Source: https://habr.com/ru/post/zh-CN433358/


All Articles