使用Python进行自动化软件测试
软件测试是开发人员通过向软件提供一些测试输入来确保软件的实际输出与所需输出匹配的过程。软件测试是一个重要的步骤,因为如果执行得当,它可以帮助开发人员在非常短的时间内找到软件中的错误。
软件测试可以分为两类,手动测试和自动化测试。自动化测试是使用脚本而不是人工执行测试。在本文中,我们将讨论一些使用Python进行自动化软件测试的方法。
让我们编写一个简单的应用程序,我们将在其上执行所有测试。
class Square:
def __init__(self, side):
""" creates a square having the given side
"""
self.side = side
def area(self):
""" returns area of the square
"""
return self.side**2
def perimeter(self):
""" returns perimeter of the square
"""
return 4 * self.side
def __repr__(self):
""" declares how a Square object should be printed
"""
s = 'Square with side = ' + str(self.side) + '\n' + \
'Area = ' + str(self.area()) + '\n' + \
'Perimeter = ' + str(self.perimeter())
return s
if __name__ == '__main__':
# read input from the user
side = int(input('enter the side length to create a Square: '))
# create a square with the provided side
square = Square(side)
# print the created square
print(square)
注意:有关函数__repr__()
的更多信息,请参阅本文。
现在我们已经准备好我们的软件,让我们看看我们项目文件夹的目录结构,然后我们将开始测试我们的软件。
---Software_Testing
|--- __init__.py (to initialize the directory as python package)
|--- app.py (our software)
|--- tests (folder to keep all test files)
|--- __init__.py
“单元测试”模块
手动测试的主要问题之一是它需要时间和精力。在手动测试中,我们通过一些输入测试应用程序,如果失败,我们要么记下它,要么针对特定的测试输入调试应用程序,然后我们重复该过程。使用unittest
,可以一次提供所有测试输入,然后您可以测试您的应用程序。最后,您会得到一份详细的报告,其中清楚地说明了所有失败的测试用例(如果有的话)。
unittest
模块有一个内置的测试框架和一个测试运行器。测试框架是编写测试用例时必须遵循的一组规则,而测试运行器是一种工具,它通过一系列设置执行这些测试并收集结果。
安装: PyPI 提供unittest
,可以使用以下命令安装 -
pip install unittest
使用:我们在Python模块 (.py) 中编写测试。要运行我们的测试,我们只需使用任何 IDE 或终端执行测试模块。
现在,让我们使用unittest
模块为上面讨论的小软件编写一些测试。
- 在名为“tests”的文件夹中创建一个名为
tests.py
的文件。 - 在
tests.py
导入unittest
。 - 创建一个名为
TestClass
的类,它继承自类unittest.TestCase
。规则 1:所有测试都写成一个类的方法,该类必须继承自类
unittest.TestCase
。 - 创建一个测试方法,如下所示。
规则 2:每个测试方法的名称都应该以“test”开头,否则会被测试运行者跳过。def test_area(self): # testing the method Square.area(). sq = Square(2) # creates a Square of side 2 units. # test if the area of the above square is 4 units, # display an error message if it's not. self.assertEqual(sq.area(), 4, f'Area is shown {sq.area()} for side = {sq.side} units')
规则 3:我们使用特殊的
assertEqual()
语句而不是Python中可用的内置assert
语句。assertEqual()
的第一个参数是实际输出,第二个参数是期望的输出,第三个参数是在两个值不同(测试失败)时显示的错误消息。 - 要运行我们刚刚定义的测试,我们需要调用
unittest.main()
方法,在“tests.py”模块中添加以下行。if __name__ == '__main__': unittest.main()
由于这些行,一旦您运行脚本“test.py”,就会调用函数
unittest.main()
并执行所有测试。
最后,“tests.py”模块应该类似于下面给出的代码。
import unittest
from .. import app
class TestSum(unittest.TestCase):
def test_area(self):
sq = app.Square(2)
self.assertEqual(sq.area(), 4,
f'Area is shown {sq.area()} rather than 9')
if __name__ == '__main__':
unittest.main()
编写完我们的测试用例后,现在让我们测试我们的应用程序是否存在任何错误。要测试您的应用程序,您只需使用命令提示符或您选择的任何 IDE 执行测试文件“tests.py”。输出应该是这样的。
.
----------------------------------------------------------------------
Ran 1 test in 0.000s
OK
在第一行,一个.
(点)表示成功的测试,而“F”表示失败的测试用例。最后, OK
消息告诉我们所有测试都已成功通过。
让我们在“tests.py”中添加更多测试并重新测试我们的应用程序。
import unittest
from .. import app
class TestSum(unittest.TestCase):
def test_area(self):
sq = app.Square(2)
self.assertEqual(sq.area(), 4,
f'Area is shown {sq.area()} rather than 9')
def test_area_negative(self):
sq = app.Square(-3)
self.assertEqual(sq.area(), -1,
f'Area is shown {sq.area()} rather than -1')
def test_perimeter(self):
sq = app.Square(5)
self.assertEqual(sq.perimeter(), 20,
f'Perimeter is {sq.perimeter()} rather than 20')
def test_perimeter_negative(self):
sq = app.Square(-6)
self.assertEqual(sq.perimeter(), -1,
f'Perimeter is {sq.perimeter()} rather than -1')
if __name__ == '__main__':
unittest.main()
.F.F
======================================================================
FAIL: test_area_negative (__main__.TestSum)
----------------------------------------------------------------------
Traceback (most recent call last):
File "tests_unittest.py", line 11, in test_area_negative
self.assertEqual(sq.area(), -1, f'Area is shown {sq.area()} rather than -1 for negative side length')
AssertionError: 9 != -1 : Area is shown 9 rather than -1 for negative side length
======================================================================
FAIL: test_perimeter_negative (__main__.TestSum)
----------------------------------------------------------------------
Traceback (most recent call last):
File "tests_unittest.py", line 19, in test_perimeter_negative
self.assertEqual(sq.perimeter(), -1, f'Perimeter is {sq.perimeter()} rather than -1 for negative side length')
AssertionError: -24 != -1 : Perimeter is -24 rather than -1 for negative side length
----------------------------------------------------------------------
Ran 4 tests in 0.001s
FAILED (failures=2)
上述测试报告中需要注意的几点是——
- 第一行表示测试 1 和测试 3 执行成功,而测试 2 和测试 4 失败
- 报告中描述了每个失败的测试用例,描述的第一行包含失败的测试用例的名称,最后一行包含我们为该测试用例定义的错误消息。
- 在报告的最后,您可以看到失败的测试次数,如果没有测试失败,报告将以
OK
结束
注意:如需进一步了解,您可以阅读unittest
的完整文档。
“nose2”模块
nose2
的目的是扩展unittest
以使测试更容易。 nose2
与使用unittest
测试框架编写的测试兼容,可以用作unittest
测试运行器的替代品。
安装: nose2
可以使用命令从 PyPI 安装,
pip install nose2
用途: nose2
没有任何测试框架,只是一个兼容unittest
测试框架的测试运行器。因此,我们将使用nose2
运行我们在上面(对于unittest
)编写的相同测试。要运行测试,我们在项目源目录中使用以下命令(在我们的例子中是“Software_Testing”),
nose2
在nose2
术语中,所有名称以“test”开头的Python模块(.py)(即test_file.py、test_1.py)都被视为测试文件。在执行时, nose2
将在位于以下一个或多个类别下的所有子目录中查找所有测试文件,
- 它们是Python包(包含“__init__.py”)。
- 名字以“test”开头的小写后,即TestFiles,tests。
- 它们被命名为“src”或“lib”。
nose2
首先加载项目中存在的所有测试文件,然后执行测试。因此,使用nose2
,我们可以自由地将我们的测试拆分到不同文件夹中的各种测试文件中并立即执行它们,这在处理大量测试时非常有用。
现在让我们了解nose2
提供的不同自定义选项,它们可以在测试过程中帮助我们。
- 更改搜索目录 –
如果我们想改变nose2
在其中搜索测试文件的目录,我们可以使用命令行参数-s
或--start-dir
来做到这一点,nose2 -s DIR_ADD DIR_NAME
这里,DIR_NAME 是我们要搜索测试文件的目录,
DIR_NAME
是DIR_ADD
的父目录DIR_NAME
对于项目源目录的地址(即如果测试目录在项目源目录中,则使用“./”本身)。
当您一次只想测试应用程序的一项功能时,这非常有用。 - 运行特定的测试用例——
使用nose2
,我们还可以使用命令行参数-s
和--start-dir
一次运行一个特定的测试,nose2 -s DIR_ADD DIR_NAME.TEST_FILE.TEST_CLASS.TEST_NAME
- TEST_NAME:测试方法的名称。
- TEST_CLASS:定义测试方法的类。
- TEST_FILE:定义测试用例的测试文件的名称,即test.py。
- DIR_NAME:测试文件所在的目录。
- DIR_ADD:DIR_NAME 的父目录相对于项目源的地址。
使用此功能,我们可以在特定输入上测试我们的软件。
- 在单个模块中运行测试 -
通过调用函数nose2
nose2.main()
也可以像unittest
一样使用nose2,就像我们在前面的示例中调用unittest.main()
一样。除了上面的基本自定义之外,
nose2
还提供了一些高级功能,例如加载各种插件和配置文件或创建自己的测试运行器。
“pytest”模块
pytest
是最流行的Python测试框架。使用pytest
,您可以测试从基本的Python脚本到数据库、API 和 UI 的任何内容。虽然pytest
主要用于 API 测试,但在本文中,我们将仅介绍pytest
的基础知识。
安装:您可以使用以下命令从 PyPI 安装pytest
,
pip install pytest
使用:在项目源码中使用以下命令调用pytest
测试运行器,
py.test
与nose2
不同, pytest
在项目目录内的所有位置查找测试文件。在pytest
术语中,任何名称以“test_”开头或以“_test”结尾的文件都被视为测试文件。让我们在文件夹“tests”中创建一个文件“test_file1.py”作为我们的测试文件。
创建测试方法:
pytest
支持在unittest
框架中编写的测试方法,但是pytest
框架提供了更简单的语法来编写测试。看下面的代码了解pytest
框架的测试方法语法。
from .. import app
def test_file1_area():
sq = app.Square(2)
assert sq.area() == 4,
f"area for side {sq.side} units is {sq.area()}"
def test_file1_perimeter():
sq = app.Square(-1)
assert sq.perimeter() == -1,
f'perimeter is shown {sq.perimeter()} rather than -1'
注意:与unittest
类似, pytest
要求所有测试名称以“test”开头。
与unittest
不同, pytest
使用默认的Python assert
语句,这使得它更易于使用。
请注意,现在“tests”文件夹包含两个文件,即“tests.py”(写在unittest
框架中)和“test_file1.py”(写在pytest
框架中)。现在让我们运行pytest
测试运行器。
py.test
您将获得与使用unittest
获得的类似报告。
============================= test session starts ==============================
platform linux -- Python 3.6.7, pytest-4.4.1, py-1.8.0, pluggy-0.9.0
rootdir: /home/manthan/articles/Software_testing_in_Python
collected 6 items
tests/test_file1.py .F [ 33%]
tests/test_file2.py .F.F [100%]
=================================== FAILURES ===================================
报告右侧的百分比显示了此时已完成的测试百分比,即 6 个测试用例中有 2 个在“test_file1.py”末尾完成。
以下是pytest
的一些基本自定义。
- 运行特定的测试文件:要仅运行特定的测试文件,请使用命令,
py.test
- 子字符串匹配:假设我们只想测试
Square
类的area()
方法,我们可以使用子字符串匹配来做到这一点,如下所示,py.test -k "area"
使用此命令
pytest
将仅执行名称中包含字符串“area”的测试,即“test_file1_area()”、“test_area()”等。 - 标记:作为子字符串匹配的替代,标记是另一种方法,我们可以使用它来运行一组特定的测试。在这种方法中,我们在要运行的测试上打上标记。观察下面给出的代码示例,
# @pytest.mark.
@pytest.mark.area def test_file1_area(): sq = app.Square(2) assert sq.area() == 4, f"area for side {sq.side} units is {sq.area()}" 在上面的代码示例中,
test_file1_area()
用标签“area”标记。所有标记了某个标签的测试方法都可以使用命令执行,py.test -m
- 并行处理:如果您有大量测试,则可以自定义
pytest
以并行运行这些测试方法。为此,您需要安装可以使用命令安装的pytest-xdist
,pip install pytest-xdist
现在您可以使用以下命令使用多处理更快地执行测试,
py.test -n 4
使用此命令
pytest
分配 4 个工作人员并行执行测试,您可以根据需要更改此数字。如果您的测试是线程安全的,您还可以使用多线程来加速测试过程。为此,您需要安装
pytest-parallel
(使用 pip)。要在多线程中运行测试,请使用以下命令,pytest --workers 4