【进阶Python】第九讲:单元测试之unittest

简介: 在开发中,为了保证项目的正常运行,能够按照预期正确的输出我们想要的结果,单元测试时必不可少的。在Python中,用于测试的库有很多,其中较为常用的就是本文的主角--unittest。本文会详细介绍unittest的两种常见用法,同时会介绍使用频率相对较少的跳过、复用等方法。

前言

单元测试(Unit Testing)是根据特定的输入数据,针对程序模块输出的正确性进行验证的工作。这些程序模块包括,

  • 单个程序
  • 函数

  • ……

我们在实现一个程序时不能仅仅实现功能方面的端到端调试,仅仅是能够从数据输入到数据输出能够实现贯通是远远不够的。还要保证每个最小模块能够按照对应的输入能够实现正确的输出,这样我们就需要设定一些测试数据来验证程序的正确性。

举个例子,参加过IT、互联网行业的同学应该都有过刷题的经历,例如,比较知名的LeetCode,我们实现一项功能,LeetCode会提供多个测试用例去验证我们程序的输出和预期输出是否形同,以此来验证我们编写程序的正确性。

可想而知,这一系列的工作是无法手动完成的,因此,很多自动化测试工具就应用而生了。在Python中比较知名的有以下几种单元测试工具,

  • unittest
  • pytest
  • doctest
  • nose
  • ……

本文的主角就是unittest,这是一款受到JUnit的启发,与其他语言中的主流单元测试框架有着相似的风格。其支持测试自动化,配置共享和关机代码测试。在Python自动化测试中使用频率较高,后续还会讲到pytestdoctest

基本示例

4.jpg

下面先看一个unittest单元测试实例,

import unittest
# 用于测试的类
class TestClass(object):
    def add(self, x, y):
        return x + y
    def is_string(self, s):
        return type(s) == str
    def raise_error(self):
        raise KeyError("test.")
# 测试用例
class Case(unittest.TestCase):
    def setUp(self):
        self.test_class = TestClass()
    def test_add_5_5(self):
        self.assertEqual(self.test_class.add(5, 5), 10)
    def test_bool_value(self):
        self.assertTrue(self.test_class.is_string("hello world!"))
    def test_raise(self):
        self.assertRaises(KeyError, self.test_class.raise_error)
    def tearDown(self):
        del self.test_class
if __name__ == "__main__":
    unittest.main()

上述这个例子就概括了unittest的基本使用,下面我们来详细剖析一下关键点。

首先,我在这里实现了一个用于测试的类TestClass,它包含3个方法,分别是用于加和的add,返回的是一个确切的数值其次,是一个判断是否为字符串的方法is_string,返回的是一个布尔型的结果;最后是抛出异常的raise_error方法,返回的是一个异常类型。

我们接下来就要测试TestClass中的3个方法是否按照我们期望的那样获取正确的结果,我们来用特定的数据作为输入,

  • add输入数据为5,5,如果功能正确返回值是10;
  • is_string输入数是'hello world!',如果功能正确返回的是True;
  • raise_error直接抛出异常;

明确了我们要测试的方法和重点,接下来就是写测试用例,在这个示例中我的测试用例是这样写的,

class Case(unittest.TestCase):
    def setUp(self):
        self.test_class = TestClass()
    def test_add_5_5(self):
        self.assertEqual(self.test_class.add(5, 5), 10)
    def test_bool_value(self):
        self.assertTrue(self.test_class.is_string("hello world!"))
    def test_raise(self):
        self.assertRaises(KeyError, self.test_class.raise_error)
    def tearDown(self):
        del self.test_class

这个测试用例包含几个需要关注的点:

  • 继承
  • 测试方法名称
  • setUp
  • tearDown
  • 断言

下面以此来说一下上述提及的这几点。

继承

unittest提供一个基类TestCase,如果我们要编写一个测试用例,就需要继承这个抽象基类,这样当我们运行测试程序时它会自动的运行这些测试用例。

测试方法的名称

测试方法要以test开头,这样测试程序能够自动找到要运行的方法,在上述例子中包含3个测试方法,

  • test_add_5_5
  • test_bool_value
  • test_raise

setUp和tearDown

setUp和tearDown有点类似于C++中的构造方法和析构方法,通俗的来讲,它们分别用于处理测试开始前和完成后要执行的命令。我们都知道C++中有构造和析构的概念,当调用一个类时,它会首先进入构造方法,用于一些初始化操作,当执行完成,它会调用析构方法,用于调用后的处理,例如清理内存和对象等。

setUp和tearDown和这个有点类似,当一个测试用例开始之前,会先进入setUp方法,当测试结束后会进入tearDown方法。

在上面测试用例中,我在setUp中用于实例化TestClass这个要被测试的类,然后在tearDown中清理对象。

断言

在上述测试用例中也用到一些用于断言的方法,它们来自于unittest基类,assertEqual()来检查预期的输出;调用assertTrue()assertFalse()来验证一个条件;调用assertRaises()来验证抛出了一个特定的异常。

执行测试程序,得到如下结果,

...
----------------------------------------------------------------------
Ran 3 tests in 0.001s
OK

可以看出,共执行了3个测试,没有出现异常现象。

如果觉得上述输出的信息比较少,不够详细,也可以在命令行执行下面命令,

$ python -m unittest -v test

其中test是脚本名称。

输出详细信息如下,

test_add_5_5 (test.Case) ... ok
test_bool_value (test.Case) ... ok
test_raise (test.Case) ... ok
----------------------------------------------------------------------
Ran 3 tests in 0.002s
OK

上述就是unittest的一种常用方法。

测试套件

上述演示了一种比较基础、简单的测试用例的使用方法,但是这样比较固化,只能自动的去查找以test开头的测试方法,然后顺序的去执行测试方法,这样显然是有点僵化的,不能按照重要程度或者我们的意愿去执行测试方法,而且遇到多个测试用例是会比较混乱。

这里要讲的测试套件能够归档测试用例,能够让我们按照指定的顺序去执行测试方法。

还是针对前面的例子来讲,在之前我们执行测试程序时通过以下方法,

if __name__ == "__main__":
    unittest.main()

我们要使用测试套件只需要修改执行部分的代码即可,

suite = unittest.TestSuite()
    tests = [
        Case('test_raise'),
        Case('test_bool_value'),
        Case('test_add_5_5')
    ]
    suite.addTests(tests)
    runner = unittest.TextTestRunner(verbosity=2)
    runner.run(suite)

这里也有几个需要注意的点,

  • 初始化套件
  • 添加测试用例
  • 执行测试用例

初始化套件

这里我们通过suite = unittest.TestSuite()来初始化套件。

添加测试用例

添加测试用例有两种方法,第一种就是上述示例中使用的这种方法,

  • 把测试用例放入一个列表中
  • suite.addTests()把测试用例列表加入套件

还有一种方法是逐个添加测试用例,

suite.addTest(Case('test_raise'))
suite.addTest(Case('test_bool_value'))
suite.addTest(Case('test_add_5_5'))

的是addTest

执行测试用例

这里需要提及一个概念,测试运行器(test runner),它是一个用于执行和输出测试结果的组件。

使用测试运行器,首先要初始化运行器runner = unittest.TextTestRunner(verbosity=2),执行器的参数列表如下,

class unittest.TextTestRunner(stream=None, descriptions=True, verbosity=1, failfast=False, buffer=False, resultclass=None, warnings=None, *, tb_locals=False)

其中stream可以用于指定输出测试信息到文件,verbosity用于指定输出详细信息。

然后用运行器运行测试套件即可,结果如下,

test_raise (__main__.Case) ... ok
test_bool_value (__main__.Case) ... ok
test_add_5_5 (__main__.Case) ... ok
----------------------------------------------------------------------
Ran 3 tests in 0.002s
OK

上述就是测试套件的用法。

跳过测试与预计的失

前面已经讲解了unittest的2种常见的使用方法,但是上述2种方法也有一些问题,就是需要会指定自动找到或者传入的测试用例全部执行,无法跳过某些测试用例,或者遇到已损坏的测试会错误的回传报告,这样我们就会用到unittest中一项比较实用的功能--跳过测试与预计的失败

和前面一样,首先给出一个例子,

class SkipCase(unittest.TestCase):
    def setUp(self):
        self.test_class = TestClass()
    @unittest.skip("Skip test.")
    def test_add_5_5(self):
        self.assertEqual(self.test_class.add(5, 5), 10)
    @unittest.skipIf(NUM < 3, "Skiped: the number is too small.")
    def test_bool_value(self):
        self.assertTrue(self.test_class.is_string("hello world!"))
    @unittest.skipUnless(NUM==3, "Skiped: the number is not equal 3.")
    def test_raise(self):
        self.assertRaises(KeyError, self.test_class.raise_error)

unittest中使用装饰器的方式来实现跳过测试与预计的失败,常用的主要有3中,

  • @unittest.skip直接跳过测试用例;
  • @unittest.skipIf当满足条件时跳过测试用例;
  • @unittest.skipUnless只有满足某一条件时不跳过,其他的都跳过;

执行上面示例,结果如下,

test_raise (__main__.SkipCase) ... skipped 'Skiped: the number is not equal 3.'
test_bool_value (__main__.SkipCase) ... skipped 'Skiped: the number is too small.'
test_add_5_5 (__main__.SkipCase) ... skipped 'Skip test.'
----------------------------------------------------------------------
Ran 3 tests in 0.002s
OK (skipped=3)

复用测试代

代码复用是开发过程中非常重要的一项工作,在项目开发中非常忌讳复用性差、冗余等问题。在单元测试中也是如此,有些测试代码可以在多个模块的测试中重复使用,这样就没必要把这个测试方法转化为每一个测试用例的子类,因此,unittest提供FunctionTestCase类。这个TestCase的子类可用于打包已有的测试函数,并支持设置前置与后置函数。

首先我们先写一个测试函数示例,

def testExample():
    test_class = TestClass()
    assert test_class.add(5, 5) == 10

假如,这个测试函数在很多测试中都会用到,我们就可以使用FunctionTestCase来复用这个测试函数来创建新的测试用例,

testcase = unittest.FunctionTestCase(testExample)

需要注意,setUp和tearDown可以作为FunctionTestCase的参数传入。

相关文章
|
1月前
|
Web App开发 前端开发 JavaScript
探索Python科学计算的边界:利用Selenium进行Web应用性能测试与优化
【10月更文挑战第6天】随着互联网技术的发展,Web应用程序已经成为人们日常生活和工作中不可或缺的一部分。这些应用不仅需要提供丰富的功能,还必须具备良好的性能表现以保证用户体验。性能测试是确保Web应用能够快速响应用户请求并处理大量并发访问的关键步骤之一。本文将探讨如何使用Python结合Selenium来进行Web应用的性能测试,并通过实际代码示例展示如何识别瓶颈及优化应用。
99 5
|
1月前
|
测试技术 持续交付 Apache
Python性能测试新风尚:JMeter遇上Locust,性能分析不再难🧐
【10月更文挑战第1天】Python性能测试新风尚:JMeter遇上Locust,性能分析不再难🧐
130 3
|
10天前
|
Java 测试技术 持续交付
【入门思路】基于Python+Unittest+Appium+Excel+BeautifulReport的App/移动端UI自动化测试框架搭建思路
本文重点讲解如何搭建App自动化测试框架的思路,而非完整源码。主要内容包括实现目的、框架设计、环境依赖和框架的主要组成部分。适用于初学者,旨在帮助其快速掌握App自动化测试的基本技能。文中详细介绍了从需求分析到技术栈选择,再到具体模块的封装与实现,包括登录、截图、日志、测试报告和邮件服务等。同时提供了运行效果的展示,便于理解和实践。
46 4
【入门思路】基于Python+Unittest+Appium+Excel+BeautifulReport的App/移动端UI自动化测试框架搭建思路
|
13天前
|
测试技术 持续交付 Apache
Python性能测试新风尚:JMeter遇上Locust,性能分析不再难🧐
Python性能测试新风尚:JMeter遇上Locust,性能分析不再难🧐
40 3
|
12天前
|
缓存 测试技术 Apache
告别卡顿!Python性能测试实战教程,JMeter&Locust带你秒懂性能优化💡
告别卡顿!Python性能测试实战教程,JMeter&Locust带你秒懂性能优化💡
28 1
|
1月前
|
测试技术 Python
自动化测试项目学习笔记(一):unittest简单运行(初始化,清除,设置测试行为)
本文介绍了Python的unittest框架的基础用法,包括测试初始化(setup)、清除(tearDown)函数的使用,以及assertEqual和assertGreaterEqual等断言方法,并展示了如何创建测试用例,强调了测试函数需以test_开头才能被运行。
62 1
自动化测试项目学习笔记(一):unittest简单运行(初始化,清除,设置测试行为)
|
11天前
|
Web App开发 测试技术 数据安全/隐私保护
自动化测试的魔法:使用Python进行Web应用测试
【10月更文挑战第32天】本文将带你走进自动化测试的世界,通过Python和Selenium库的力量,展示如何轻松对Web应用进行自动化测试。我们将一起探索编写简单而强大的测试脚本的秘诀,并理解如何利用这些脚本来确保我们的软件质量。无论你是测试新手还是希望提升自动化测试技能的开发者,这篇文章都将为你打开一扇门,让你看到自动化测试不仅可行,而且充满乐趣。
|
1月前
|
测试技术 Python
自动化测试项目学习笔记(三):Unittest加载测试用例的四种方法
本文介绍了使用Python的unittest框架来加载测试用例的四种方法,包括通过测试用例类、模块、路径和逐条加载测试用例。
61 0
自动化测试项目学习笔记(三):Unittest加载测试用例的四种方法
|
1月前
|
缓存 测试技术 Apache
告别卡顿!Python性能测试实战教程,JMeter&Locust带你秒懂性能优化💡
【10月更文挑战第1天】告别卡顿!Python性能测试实战教程,JMeter&Locust带你秒懂性能优化💡
60 4
|
1月前
|
JSON 测试技术 数据库
Python 中的黑盒测试器
Python 中的黑盒测试器
12 0