学习与Pythons Unittest一起工作

89 阅读6分钟

与Pythons Unittest一起工作

在软件工程中,单元测试是指用于自动检查软件各个部分/单元中的错误的程序。单元测试是软件开发生命周期的一个重要阶段。为了进行测试,开发者要指定一组测试用例及其预期结果,以便进行比较。

测试简介

大多数编程语言提供内置的单元测试框架。在这篇文章中,我们将重点介绍Python的unittest。回归测试是指对软件进行重新测试,以确保其在做出改变后(重构代码后)能很好地工作。在这篇文章中,我们将看到如何使用 unittest 模块来进行回归测试。

前提条件

要跟上这篇文章,你将需要

  1. 对Python函数和类的基本了解。
  2. 一个文本编辑器。我将使用[Visual Studio Code]。
  3. 安装了[Python]。

让我们开始创建两个文件:mathfns.pymain.py ,使用下面的项目结构。

project-root
    |
    --- mathfns.py
    --- main.py

让我们编写代码

mathfns.py 文件将存储我们要进行单元测试的单元。这些单元是基本的数学函数,用于将一个给定的数字x ,得到一个数字的平方x ,并将两个数字num (分子)和den (分母)相除。

# mathfns.py

def double(x):
   return 2 * x

def square(x):
   return x * x

# divide numerator by denominator
def divide(num, den):
   return num / den

单元测试

# main.py

from mathfns import double, square, divide
import unittest

class TestMathFunctions(unittest.TestCase):
   def test_double(self):
      # used to check that double(8) returns 16
      self.assertEqual(double(8), 16)

      # used to check that double(8) returns a number not equal to 15
      self.assertNotEqual(double(8), 15)
 
    def test_square(self):
       # used to check that square(7) returns 49
       self.assertEqual(square(7), 49)
    
    def test_divide(self):
       # we want to make sure that the divide unit raises a ZeroDivisionError
       # when one attempts to divide a number by zero.
       with self.assertRaises(ZeroDivisionError):
         divide(42, 0)

if __name__ == '__main__':
   # unittest.main() serves as the main entry point to run the unit tests.
   # unit tests are not performed without this function call
   unittest.main()

main.py 文件将存储我们的单元测试。我们从上面定义的mathfns 脚本中导入单元double,square, 和divide 。然后我们从 Python 的标准库中导入unittest 模块。

TestMathFunctions 类从unittest.TestCase 类继承了单元测试功能。

它提供了三个方法。

  1. test_double 对 单元进行单元测试。double
  2. test_square 对 单元进行单元测试。square
  3. test_divide 对 单元进行单元测试。divide

为了使单元测试具有自我描述性,使用了以下命名惯例:测试名称应该采用test_UnitName 的格式。在这种情况下,UnitName 指的是被测试的单元。在每个单元测试中,我们使用断言方法来检查任何报告错误。

要运行单元测试,请按以下步骤进行。

>>> python3 -m unittest main.py

输出将显示所有三个测试都被正确执行。

>>> python3 -m unittest main.py
...
----------------------------------------------------------------------
Ran 3 tests in 0.000s

OK

输出类型

运行测试后,unittest模块将有两种类型的输出。

  1. OK:这表明所有测试都成功运行了,如上图所示。
  2. FAILED (failures=n):这显示n 测试失败。unittest 然后打印出相关的失败信息。

失败的例子

我们已经看到了单元测试正确运行时的情况。现在,让我们来看看当其中一个单元测试失败时会发生什么。为此,我们改变了double 函数,将数字x ,而不是翻倍,如下图所示。

def double(x):
   return 4 * x

当我们运行单元测试时,我们得到以下输出。

>>> python3 -m unittest main.py
.F.
======================================================================
FAIL: test_double (__main__.TestMathFunctions)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "main.py", line 8, in test_double
    self.assertEqual(double(8), 16)
AssertionError: 32 != 16

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

FAILED (failures=1)

在我们进行的3个测试中,self.assertEqual(double(8), 16) 产生了一个错误,因为32 != 16 。这说明了单元测试是如何用于回归测试的。

要看到更详细的信息,请在存放单元测试的文件前使用-v 标志。v 代表verbose

>>> python3 -m unittest -v main.py
test_divide (tests.TestMathFunctions) ... ok
test_double (tests.TestMathFunctions) ... FAIL
test_square (tests.TestMathFunctions) ... ok

======================================================================
FAIL: test_double (tests.TestMathFunctions)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "main.py", line 8, in test_double
    self.assertEqual(double(8), 16)
AssertionError: 32 != 16

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

FAILED (failures=1)

错误信息

错误信息对于未来的代码调试非常有用。它们也为质量保证测试人员和其他开发人员提供了文件。

例如,要在self.assertEqual(double(8), 16) ,添加一个错误信息,改变你的代码来匹配这个。

   def test_double(self):
      self.assertEqual(double(8), 16, "the function should return two times the number provided")

输出结果将是。

>>> python3 -m unittest main.py
.F.
======================================================================
FAIL: test_double (__main__.TestMathFunctions)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "main.py", line 8, in test_double
    self.assertEqual(double(8), 16, "the function should return two times the number provided")
AssertionError: 32 != 16 : the function should return two times the number provided

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

FAILED (failures=1)

如上所示,我们添加的错误信息更好地描述了AssertionError 。这使它更容易被理解。

断言方法

到目前为止,我们已经看到了assertEqual(a, b) 方法、assertNotEqual(a, b) 方法和assertRaises(x) 方法。除此以外,unittest模块还提供了这些额外的断言方法。

方法结果
assertTrue(x)检查一个布尔值x 是否等于True
assertFalse(x)检查一个布尔值x 是否等于False
assertIn(a,b)检查一个值a 是否存在于一个列表b 。例如,当a=2 和...时,这个检查应该通过。b=[1,2,3]
assertNottIn(a,b)检查一个值a 不存在于一个列表b 。例如,当a=2b=[1,2,3] 时,这个检查应该失败,因为2 存在于列表中b
assertIsNone(x)检查一个值a 是否等于None
assertIsNotNone(x)检查一个值a 不等于None
assertIs(a, b)检查ab 是否为同一对象
assertIsNot(a, b)检查ab 是不同的对象。
assertIsInstance(a,b)检查ab 类的一个实例。例如,如果a="Hello" ,和b=str ,检查应该通过,因为a 是一个字符串。
assertNotIsInstance(a, b)检查a 是不是一个类的实例。b

单元测试的好处

单元测试是软件开发生命周期的一个重要步骤,它提供的好处有:

  • 发现错误的有效和自动化的方法。
  • 确保一个单元在重构后能正常工作(回归测试)。
  • 在开发的早期阶段发现bug,可以降低项目成本。
  • 它可以帮助新的开发人员熟悉单元的工作情况。
  • 它提供了代码质量的保证,因为它们显示了代码的正确运行。

unittest和其他Python测试框架

最流行的第三方单元测试框架依次包括pytest和nose。然而,unittest更适合初学者,因为它有一个浅显的学习曲线。

例如,它不使用像pytest这样的框架中使用的复杂注释。此外,它不需要安装,因为它已经内置于Python的标准库中。

与第三方框架相比,unittest确实有一些限制。

这些限制包括。

  • 它需要更多的模板代码才能开始使用。
  • 它不支持插件。

总结

在这篇文章中,我们了解了单元测试、回归测试,以及如何使用Python的unittest模块。