📜  使用Unittest在Python中进行单元测试

📅  最后修改于: 2020-09-02 04:43:53             🧑  作者: Mango

介绍

在几乎所有领域中,产品在投放市场之前都经过了彻底的测试,以确保产品的质量和预期效果。

医药,化妆品,车辆,电话,笔记本电脑均经过测试,以确保它们维持承诺给消费者的一定质量。考虑到软件在日常生活中的影响力和影响力,在将软件发布给用户之前,我们必须对软件进行全面测试,以免在使用过程中出现问题。

有多种方法和方法来测试我们的软件,在本文中,我们将集中于使用Unittest框架测试Python程序。

单元测试与其他形式的测试

有多种方法来测试软件,很多被大量分为功能性非功能性测试。

  • 非功能测试:旨在验证和检查软件的非功能性方面,例如可靠性,安全性,可用性和可伸缩性。非功能测试的示例包括负载测试压力测试
  • 功能测试:涉及针对功能要求测试我们的软件,以确保其提供了所需的功能。例如,我们可以通过模拟该场景并检查电子邮件,来测试我们的购物平台在下订单后是否向用户发送电子邮件。

单元测试属于功能测试以及集成测试回归测试

单元测试是一种测试方法,其中将软件分解为不同的组件(单元),并且在功能上且与其他单元或模块隔离地测试每个单元。

这里的单元是指系统中能够实现单一功能且可测试的最小部分。单元测试的目的是验证系统的每个组件是否按预期执行,进而确认整个系统满足并满足功能要求。

单元测试通常在集成测试之前执行,因为为了验证系统的各个部分是否可以正常工作,我们必须首先验证它们是否分别按预期工作。通常也由开发人员在开发过程中构建各个组件来执行。

单元测试的好处

单元测试是有益的,因为它可以在开发过程的早期修正错误和问题,并最终加快速度。

与在集成测试过程中或在生产过程中修复错误相比,修复在单元测试期间发现的错误的成本也较低。

单元测试还通过定义系统的每个部分通过良好编写和记录的测试来做什么,还可以作为项目的文档。在重构系统或添加功能时,单元测试有助于防止破坏现有功能的更改。

单元测试框架

灵感来自于Java的JUnit测试框架unittest是针对自带因为Python 2.1 Python发布捆绑Python程序测试框架。有时称为PyUnit。该框架支持测试的自动化和聚合以及它们的通用设置和关闭代码。

它通过以下概念来实现这一目标,甚至更多:

  • 测试装置:定义执行测试所需的准备工作以及在测试结束后需要执行的任何操作。固定装置可以包括数据库设置和连接,临时文件或目录的创建以及测试完成后的后续清理或删除文件。
  • 测试用例:指在给定场景中使用特定输入检查特定响应的单个测试。
  • 测试套件:代表相关的测试案例的汇总,应该一起执行。
  • 测试运行器:协调测试的执行,并通过图形用户界面,终端或写入文件的报告向用户提供测试过程的结果。

unittest并不是唯一的Python测试框架,其他包括PytestRobot框架,BDD的LettuceBehave框架

 

行动中的单元测试框架

我们将unittest通过构建一个简单的计算器应用程序并编写测试以验证其是否按预期工作来探索该框架。我们将使用测试驱动的开发过程,首先从测试开始,然后实施使测试通过的功能。

即使在虚拟环境中开发Python应用程序是一个好习惯,但对于本示例来说unittest,由于Python发行版附带了该示例,因此它不是必需的,并且我们不需要任何其他外部程序包即可构建计算器。

我们的计算器将在两个整数之间执行简单的加,减,乘和除运算。这些要求将指导我们使用该unittest框架进行功能测试。

我们将分别测试计算器支持的四个操作,并在单独的测试套件中编写每个测试的测试,因为特定操作的测试预计将一起执行。我们的测试套件将保存在一个文件中,我们的计算器将保存在单独的文件中。

我们的计算器将是一SimpleCalculator类具有处理期望的四个操作的功能的类。让我们通过在我们的加法运算中编写测试开始测试test_simple_calculator.py

import unittest
from simple_calculator import SimpleCalculator

class AdditionTestSuite(unittest.TestCase):
    def setUp(self):
        """ Executed before every test case """
        self.calculator = SimpleCalculator()

    def tearDown(self):
        """ Executed after every test case """
        print("\ntearDown executing after the test case. Result:")

    def test_addition_two_integers(self):
        result = self.calculator.sum(5, 6)
        self.assertEqual(result, 11)

    def test_addition_integer_string(self):
        result = self.calculator.sum(5, "6")
        self.assertEqual(result, "ERROR")

    def test_addition_negative_integers(self):
        result = self.calculator.sum(-5, -6)
        self.assertEqual(result, -11)
        self.assertNotEqual(result, 11)

# Execute all the tests when the file is executed
if __name__ == "__main__":
    unittest.main()

我们首先导入unittest模块并AdditionTestSuite为添加操作创建测试套件()。

在其中,我们创建一个每个测试用例之前都要setUp()调用的方法,以创建将用于执行计算的对象。SimpleCalculator

tearDown()方法每个测试用例之后执行并且由于我们目前没有太多用处,因此我们将使用它来打印每个测试的结果。

的功能test_addition_two_integers()test_addition_integer_string()并且test_addition_negative_integers()是我们的测试案例。期望计算器将两个正整数或负整数相加并返回总和。当使用整数和字符串表示时,我们的计算器应该返回错误。

assertEqual()assertNotEqual()是用来验证我们的计算器的输出功能。该assertEqual()函数检查提供的两个值是否相等,在我们的例子中,我们期待的总和5,并6成为11,所以我们会比较这对我们的计算器返回的值。

如果两个值相等,则测试已通过。所提供的其他断言功能unittest包括:

  • assertTrue(a):检查提供的表达式是否为 true
  • assertGreater(a, b):检查是否a大于b
  • assertNotIn(a, b):检查是否ab
  • assertLessEqual(a, b):检查是否a小于或等于b
  • 等等…

该备忘单中可以找到这些断言的列表。

当我们执行测试文件时,这是输出: 

$ python3 test_simple_calulator.py

tearDown executing after the test case. Result:
E
tearDown executing after the test case. Result:
E
tearDown executing after the test case. Result:
E
======================================================================
ERROR: test_addition_integer_string (__main__.AdditionTestSuite)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "test_simple_calulator.py", line 22, in test_addition_integer_string
    result = self.calculator.sum(5, "6")
AttributeError: 'SimpleCalculator' object has no attribute 'sum'

======================================================================
ERROR: test_addition_negative_integers (__main__.AdditionTestSuite)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "test_simple_calulator.py", line 26, in test_addition_negative_integers
    result = self.calculator.sum(-5, -6)
AttributeError: 'SimpleCalculator' object has no attribute 'sum'

======================================================================
ERROR: test_addition_two_integers (__main__.AdditionTestSuite)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "test_simple_calulator.py", line 18, in test_addition_two_integers
    result = self.calculator.sum(5, 6)
AttributeError: 'SimpleCalculator' object has no attribute 'sum'

----------------------------------------------------------------------
Ran 3 tests in 0.001s

FAILED (errors=3)

在输出的顶部,tearDown()通过打印指定的消息,我们可以看到函数的执行。接下来E是执行测试所产生的提示和错误消息。

测试有三种可能的结果,它可以通过,失败或遇到错误。该unittest框架通过使用以下三种方式来指示:

  • 句号(.):表示通过测试
  • 字母“ F”:表示测试失败
  • 字母“ E”:表示测试执行期间发生错误

在我们的例子中,我们看到的字母E,意味着我们的测试遇到了执行测试时发生的错误。我们收到错误是因为我们尚未实现addition计算器的功能:

class SimpleCalculator:
    def sum(self, a, b):
        """ Function to add two integers """
        return a + b

现在,我们的计算器已准备好将两个数字相加,但是要确保它能按预期运行,请让我们tearDown()从测试中删除该函数,然后再次运行测试:

$ python3 test_simple_calulator.py
E..
======================================================================
ERROR: test_addition_integer_string (__main__.AdditionTestSuite)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "test_simple_calulator.py", line 22, in test_addition_integer_string
    result = self.calculator.sum(5, "6")
  File "/Users/robley/Desktop/code/python/unittest_demo/src/simple_calculator.py", line 7, in sum
    return a + b
TypeError: unsupported operand type(s) for +: 'int' and 'str'

----------------------------------------------------------------------
Ran 3 tests in 0.002s

FAILED (errors=1)

我们的错误已从3个减少到只有1个。第一行的报告摘要E..指示一个测试导致错误并且无法完成执行,其余两项通过。为了进行第一次测试,我们必须重构求和函数,如下所示:

    def sum(self, a, b):
        if isinstance(a, int) and isinstance(b, int):
            return a + b

当我们再运行一​​次测试时:

$ python3 test_simple_calulator.py
F..
======================================================================
FAIL: test_addition_integer_string (__main__.AdditionTestSuite)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "test_simple_calulator.py", line 23, in test_addition_integer_string
    self.assertEqual(result, "ERROR")
AssertionError: None != 'ERROR'

----------------------------------------------------------------------
Ran 3 tests in 0.001s

FAILED (failures=1)

这次,我们的sum函数执行完成,但是测试失败。这是因为当输入之一不是整数时,我们没有返回任何值。我们的断言与之比较NoneERROR并且由于它们不相等,因此测试失败。为了使测试通过,我们必须在sum()函数中返回错误:

def sum(self, a, b):
    if isinstance(a, int) and isinstance(b, int):
        return a + b
    else:
        return "ERROR"

当我们运行测试时:

$ python3 test_simple_calulator.py
...
----------------------------------------------------------------------
Ran 3 tests in 0.000s

OK

现在,我们所有的测试都通过了,我们获得了3个完全停止信号,以表明我们所有3个关于附加功能的测试都通过了。减法,乘法和除法测试套件也以类似的方式实现。

我们还可以测试是否引发异常。例如,当数字除以零时,ZeroDivisionError会引发异常。在我们的中DivisionTestSuite,我们可以确认是否引发了异常:

class DivisionTestSuite(unittest.TestCase):
    def setUp(self):
        """ Executed before every test case """
        self.calculator = SimpleCalculator()

    def test_divide_by_zero_exception(self):
        with self.assertRaises(ZeroDivisionError):
            self.calculator.divide(10, 0)

test_divide_by_zero_exception()会执行divide(10, 0)该异常确实提高了我们的计算器和确认的功能。我们可以DivisionTestSuite隔离执行,如下所示:

$ python3 -m unittest test_simple_calulator.DivisionTestSuite.test_divide_by_zero_exception
.
----------------------------------------------------------------------
Ran 1 test in 0.000s

OK

完整的除法功能测试套件可在下面的要点中找到,用于乘法和减法功能的测试套件。

结论

在本文中,我们探索了该unittest框架并确定了在开发Python程序时使用该框架的情况。与其他测试框架相反,该unittest框架(也称为PyUnit)在默认情况下随附Python发行版。在TDD方式中,我们为一个简单的计算器编写了测试,执行了测试,然后实现了使测试通过的功能。

unittest框架提供了创建和组合测试用例的功能,并根据预期输出检查计算器的输出以验证其是否按预期工作。