📜  使用Python进行自动化软件测试

📅  最后修改于: 2022-05-13 01:54:42.856000             🧑  作者: Mango

使用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模块为上面讨论的小软件编写一些测试。

  1. 在名为“tests”的文件夹中创建一个名为tests.py的文件。
  2. tests.py导入unittest
  3. 创建一个名为TestClass的类,它继承自类unittest.TestCase

    规则 1:所有测试都写成一个类的方法,该类必须继承自类unittest.TestCase

  4. 创建一个测试方法,如下所示。
    规则 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()的第一个参数是实际输出,第二个参数是期望的输出,第三个参数是在两个值不同(测试失败)时显示的错误消息。

  5. 要运行我们刚刚定义的测试,我们需要调用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提供的不同自定义选项,它们可以在测试过程中帮助我们。

  1. 更改搜索目录 –
    如果我们想改变nose2在其中搜索测试文件的目录,我们可以使用命令行参数-s--start-dir来做到这一点,
    nose2 -s DIR_ADD DIR_NAME

    这里,DIR_NAME 是我们要搜索测试文件的目录, DIR_NAMEDIR_ADD的父目录DIR_NAME对于项目源目录的地址(即如果测试目录在项目源目录中,则使用“./”本身)。
    当您一次只想测试应用程序的一项功能时,这非常有用。

  2. 运行特定的测试用例——
    使用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 的父目录相对于项目源的地址。

    使用此功能,我们可以在特定输入上测试我们的软件。

  3. 在单个模块中运行测试 -
    通过调用函数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的一些基本自定义。

  1. 运行特定的测试文件:要仅运行特定的测试文件,请使用命令,
    py.test 
  2. 子字符串匹配:假设我们只想测试Square类的area()方法,我们可以使用子字符串匹配来做到这一点,如下所示,
    py.test -k "area"

    使用此命令pytest将仅执行名称中包含字符串“area”的测试,即“test_file1_area()”、“test_area()”等。

  3. 标记:作为子字符串匹配的替代,标记是另一种方法,我们可以使用它来运行一组特定的测试。在这种方法中,我们在要运行的测试上打上标记。观察下面给出的代码示例,
    # @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 
  4. 并行处理:如果您有大量测试,则可以自定义pytest以并行运行这些测试方法。为此,您需要安装可以使用命令安装的pytest-xdist
    pip install pytest-xdist

    现在您可以使用以下命令使用多处理更快地执行测试,

    py.test -n 4

    使用此命令pytest分配 4 个工作人员并行执行测试,您可以根据需要更改此数字。

    如果您的测试是线程安全的,您还可以使用多线程来加速测试过程。为此,您需要安装pytest-parallel (使用 pip)。要在多线程中运行测试,请使用以下命令,

    pytest --workers 4