PyUnit(unittest) 是 Python 自带的单元测试框架,用于编写和运行可重复的测试。PyUnit 是 xUnit 体系的一个成员,xUnit 是众多测试框架的总称,PyUnit 主要用于进行白盒测试和回归测试。

如果你使用的是 2.1 或更早版本的 Python,则可能需要自行下载和安装 PyUnit,现在的开发者通常不需要操心这些事情。

通过 PyUnit 可以让测试具有持久性,测试与开发同步进行,测试代码与开发代码一同发布。使用 PyUnit 具有如下好处:

  • 可以使测试代码与产品代码分离。
  • 针对某一个类的测试代码只需要进行较少的改动,便可以应用于另一个类的测试。
  • PyUnit 开放源代码,可以进行二次开发,方便对 PyUnit 的扩展。

PyUnit 是一个简单、易用的测试框架,其具有如下特征:

  • 使用断言方法判断期望值和实际值的差异,返回 bool 值。
  • 测试驱动设备可使用共同的初始化变量或实例。
  • 测试包结构便于组织和集成运行。

PyUnit (unittest) 的用法

所有测试的本质其实都是一样的,都是通过给定参数来执行函数,然后判断函数的实际输出结果和期望输出结果是否一致。

PyUnit 测试与其他 xUnit 的套路一样,基于断言机制来判断函数或方法的实际输出结果和期望输出结果是否一致,测试用例提供参数来执行函数或方法,获取它们的执行结果,然后使用断言方法来判断该函数或方法的输出结果与期望输出结果是否一致,如果一致则说明测试通过;如果不一致则说明测试不通过。

目前还有一种流行的开发方式叫作测试驱动开发,这种方式强调先编写测试用例,然后再编写函数和方法。假如程序要开发满足 A 功能的 fun_a() 函数,采用测试驱动开发的步骤如下:

  1. 为 fun_a() 函数编写测试用例,根据业务要求,使用大量不同的参数组合来执行 fun_a() 函数,并断言该函数的执行结果与业务期望的执行结果匹配。
  2. 编写、修改 fun_a() 函数。
  3. 运行 fun_a() 函数的测试用例,如果测试用例不能完全通过;则重复第 2 步和第 3 步,直到 fun_a() 的所有测试用例全部通过。

测试驱动开发强调结果导向,也就是在开发某个功能之前,先定义好该功能的最终结果(测试用例关注函数的执行结果),然后再去开发该功能。就像建筑工人在砌墙之前,要先拉好一根笔直的绳子(作用相当于测试用例),然后再开始砌墙,这样砌出来的墙就会符合标准。所以说测试驱动开发确实是一种不错的开发方式。

下面开发一个简单的 fk_math.py 程序,该程序包含两个函数,分别用于计算一元一次方程的解和二元一次方程的解。

  1. def one_equation(a , b):
  2. '''
  3. 求一元一次方程a * x + b = 0的解
  4. 参数a - 方程中变量的系数
  5. 参数b - 方程中的常量
  6. 返回 方程的解
  7. '''
  8. # 如果a = 0,则方程无法求解
  9. if a == 0:
  10. raise ValueError("参数错误")
  11. # 返回方程的解
  12. else:
  13. # return -b / a # ①
  14. return b / a
  15. def two_equation(a , b , c):
  16. '''
  17. 求一元二次方程a * x * x + b * x + c = 0的解
  18. 参数a - 方程中变量二次幂的系数
  19. 参数b - 方程中变量的系数
  20. 参数c - 方程中的常量
  21. 返回 方程的根
  22. '''
  23. # 如果a == 0,变成一元一次方程
  24. if a == 0:
  25. raise ValueError("参数错误")
  26. # 有理数范围内无解
  27. elif b * b - 4 * a * c < 0:
  28. raise ValueError("方程在有理数范围内无解")
  29. # 方程有唯一的解
  30. elif b * b - 4 * a * c == 0:
  31. # 使用数组返回方程的解
  32. return -b / (2 * a)
  33. # 方程有两个解
  34. else:
  35. r1 = (-b + (b * b - 4 * a * c) ** 0.5) / 2 / a
  36. r2 = (-b - (b * b - 4 * a * c) ** 0.5) / 2 / a
  37. # 方程的两个解
  38. return r1, r2

在定义好上面的 tk_math.py 程序之后,该程序就相当于一个模块,接下来为该模块编写单元测试代码。

unittest 要求单元测试类必须继承 unittest.TestCase,该类中的测试方法需要满足如下要求:

  • 测试方法应该没有返回值。
  • 测试方法不应该有任何参数。
  • 测试方法应以test 开头。

下面是测试用例的代码:

  1. import unittest
  2. from fk_math import *
  3. class TestFkMath(unittest.TestCase):
  4. # 测试一元一次方程的求解
  5. def test_one_equation(self):
  6. # 断言该方程求解应该为-1.8
  7. self.assertEqual(one_equation(5 , 9) , -1.8)
  8. # 断言该方程求解应该为-2.5
  9. self.assertTrue(one_equation(4 , 10) == -2.5 , .00001)
  10. # 断言该方程求解应该为27/4
  11. self.assertTrue(one_equation(4 , -27) == 27 / 4)
  12. # 断言当a == 0时的情况,断言引发ValueError
  13. with self.assertRaises(ValueError):
  14. one_equation(0 , 9)
  15. # 测试一元二次方程的求解
  16. def test_two_equation(self):
  17. r1, r2 = two_equation(1 , -3 , 2)
  18. self.assertCountEqual((r1, r2), (1.0, 2.0), '求解出错')
  19. r1, r2 = two_equation(2 , -7 , 6)
  20. self.assertCountEqual((r1, r2), (1.5, 2.0), '求解出错')
  21. # 断言只有一个解的情形
  22. r = two_equation(1 , -4 , 4)
  23. self.assertEqual(r, 2.0, '求解出错')
  24. # 断言当a == 0时的情况,断言引发ValueError
  25. with self.assertRaises(ValueError):
  26. two_equation(0, 9, 3)
  27. # 断言引发ValueError
  28. with self.assertRaises(ValueError):
  29. two_equation(4, 2, 3)

上面测试用例中使用断言方法判断函数的实际输出结果与期望输出结果是否一致,如果一致则表明测试通过,否则表明测试失败。

在上面的测试用例中,在测试 one_equation() 方法时传入了四组参数。至于此处到底需要传入几组参数进行测试,关键取决于测试者要求达到怎样的逻辑覆盖程度,随着测试要求的提高,此处可能需要传入更多的测试参数。当然,此处只是介绍 PyUnit 的用法示例,并未刻意去达到怎样的逻辑覆盖,这一点请务必留意。

在测试某个方法时,如果实际测试要求达到某种覆盖程度,那么在编写测试用例时必须传入多组参数来进行测试,使得测试用例能达到指定的逻辑覆盖。

unittest.TestCase 内置了大量 assertXxx 方法来执行断言,其中最常用的断言方法如表 1 所示。

表 1 TestCase 中最常用的断言方法
断言方法 检查条件
assertEqual(a, b) a == b
assertNotEqual(a, b) a != b
assertTrue(x) bool(x) is True
assertFalse(x) bool(x) is False
assertIs(a, b) a is b
assertIsNot(a, b) a is not b
assertIsNone(x) x is None
assertIsNotNone(x) x is not None
assertIn(a, b) a in b
assertNotIn(a, b) a not in b
assertlsInstance(a, b) isinstance(a, b)
assertNotIsInstance(a, b) not isinstance(a, b)

除了上面这些断言方法,如果程序要对异常、错误、警告和日志进行断言判断,TestCase 提供了如表 2 所示的断言方法。

表 2 TestCase 包含的与异常、错误、警告和日志相关的断言方法
断言方法 检查条件
assertRaises(exc, fun, *args, **kwds) fun(*args, **kwds) 引发 exc 异常
assertRaisesRegex(exc, r, fun, *args, **kwds) fun(*args, **kwds) 引发 exc 异常,且异常信息匹配 r 正则表达式
assertWarns(warn, fun, *args, **kwds) fun(*args, **kwds) 引发 warn 警告
assertWamsRegex(warn, r, fun, *args, **kwds) fun(*args, **kwds) 引发 warn 警告,且警告信息匹配 r 正则表达式
assertLogs(logger, level) With 语句块使用日志器生成 level 级别的日志

TestCase 还包含了如表 3 所示的断言方法用于完成某种特定检查。

表 3 TestCase 包含的用于完成某种特定检查的断言方法
断言方法 检查条件
assertAlmostEqual(a, b) round(a-b, 7) == 0
assertNotAlmostEqual(a, b) round(a-b, 7) != 0
assertGreater(a, b) a > b
assertGreaterEqual(a, b) a >= b
assertLess(a, b) a < b
assertLessEqual(a, b) a <= b
assertRegex(s, r) r.search(s)
assertNotRegex(s, r) not r.search(s)
assertCountEqual(a, b) a、b 两个序列包含的元素相同,不管元素出现的顺序如何

当测试用例使用 assertEqual() 判断两个对象是否相等时,如果被判断的类型是字符串、序列、列表、元组、集合、字典,则程序会自动改为使用如表 4 所示的断言方法进行判断。换而言之,如表 4 所示的断言方法其实没有必要使用,unittest 模块会自动应用它们。

表 4 TestCase 包含的针对特定类型的断言方法
断言方法 用于比较的类型
assertMultiLineEqual(a, b) 字符串(string)
assertSequenceEqual(a, b) 序列(sequence)
assertListEqual(a, b) 列表(list)
assertTupleEqual(a, b) 元组(tuple)
assertSetEqual(a, b) 集合(set 或 frozenset)
assertDictEqual(a, b) 字典(dict)

运行测试

在编写完测试用例之后,可以使用如下两种方式来运行它们:

  1. 通过代码调用测试用例。程序可以通过调用 unittest.main() 来运行当前源文件中的所有测试用例。例如,在上面的测试用例中增加如下代码:

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

  2. 使用 unittest 模块运行测试用例。使用该模块的语法格式如下:

    python -m unittest 测试文件

    在使用 python -m unittest 命令运行测试用例时,如果没有指定测试用例,该命令将自动查找并运行当前目录下的所有测试用例。因此,程序也可直接使用如下命令来运行所有测试用例:

    py -m unittest

采用上面任意一种方式来运行测试用例,均可以看到如下输出结果:

..
Ran 2 tests in 0.000s

OK

在上面输出结果的第一行可以看到两个点,这里的每个点都代表一个测试用例(每个以 test_ 开头的方法都是一个真正独立的测试用例)的结果。由于上面测试类中包含了两个测试用例,因此此处看到两个点,其中点代表测试用例通过。此处可能出现如下字符:

  • .:代表测试通过。
  • F:代表测试失败,F 代表 failure。
  • E:代表测试出错,E 代表 error。
  • s:代表跳过该测试,s 代表 skip。

在上面输出结果的横线下面看到了“Ran 2 tests in O.OOOs”提示信息,这行提示信息说明本次测试运行了多少个测试用例。如果看到下面提示 OK,则表明所有测试用例均通过。

上面的测试用例都可通过,是因为 fk_math.py 程序没有错误。如果将 fk_math.py 程序中的 ① 号代码故意修改为出错,假如将 ① 号代码修改为 return b/a,再次运行上面的测试用例,将会看到如下输出结果:

F.
======================================================================
FAIL: test_one_equation (__main__.TestFkMath)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "E:\test_fk_math.py", line 24, in test_one_equation
    self.assertEqual(one_equation(5 , 9) , -1.8)
AssertionError: 1.8 != -1.8
----------------------------------------------------------------------
Ran 2 tests in 0.001s

FAILED (failures=1)

此时看到第一行的输出信息为 F,这表明第一个测试用例失败,第二个测试用例成功。

接下来在两条横线之间可以看到断言错误的 Traceback 信息,以及函数运行的实际输出结果和期望输出结果的差异。信息提示该函数运行的实际输出结果是 1.8,但期望输出结果是 -1.8。

PyUnit (unittest) 的用法的更多相关文章

  1. unittest 的用法

    一.discover方法 discover方法可以根据标准加载用例,并将结果返回给测试套件(suite),start_dir:待测试的目录,pattern:测试用例文件名的匹配规. 如: start_ ...

  2. Python unittest(PyUnit)单元测试框架

    PyUnit(unittest) 是 Python 自带的单元测试框架,用于编写和运行可重复的测试.PyUnit 是 xUnit 体系的一个成员,xUnit 是众多测试框架的总称,PyUnit 主要用 ...

  3. 记录python接口自动化测试--unittest框架基本应用(第二目)

    在第一目里写了几个简单demo,并把调用get和post请求的方法封装到了一个类里,这次结合python自带的unittest框架,用之前封装的方法来写一个接口测试demo 1.unittest简单用 ...

  4. python unittest框架装饰器

    要说单元测试和UI自动化之间的是什么样的一个关系,说说我个人的一些心得体会吧,我并没有太多的这方面经验,由于工作本身就用的少,还有就是功能测试点点对于我这种比较懒惰的人来说,比单元测试复杂...思考单 ...

  5. unittest单元测试,基于java的junit测试框架

    import unittestclass study(unittest.TestCase): def testXia(self): self.assertEqual((3*4),20) def tes ...

  6. <自动化测试>之<unittest框架使用1>

    要说单元测试和UI自动化之间的是什么样的一个关系,说说我个人的一些心得体会吧,我并没有太多的这方面经验,由于工作本身就用的少,还有就是功能测试点点对于我这种比较懒惰的人来说,比单元测试复杂...思考单 ...

  7. unittest学习

    unittest的四大特点 TestCase:测试用例.所有的用例都是直接继承与UnitTest.TestCase类. TestFixture:测试固件.setUp和tearDown分别作为前置条件和 ...

  8. selenium+python笔记3

    #!/usr/bin/env python # -*- coding: utf-8 -*- """ @desc:学习unittest的用法 注意setUp/setUpCl ...

  9. 改善python程序的建议[转]

    <编写高质量代码 改善Python程序的91个建议> <编写高质量代码 改善Python程序的91个建议>读后程序学习小结 - BigDeng_2014的专栏 - CSDN博客 ...

随机推荐

  1. cesium左侧列表定位目标

    cesium左侧列表定位目标 功能:根据左侧列表经纬度等信息的值,进行搜索定位. 列表: 1  点击清除按钮可以清空所有input的值 2  点击查找可以定位到位置,如果输入的值不在范围内,会有弹出框 ...

  2. CentOS7 启动docker.service失败

    背景:阿里云服务器安装了docker服务,并且更改了仓库位置 需求:让docker正常启动 方法: 一.修改/etc/docker/daemon.json文件后缀 当向该文件中写入仓库配置时,该文件后 ...

  3. python之数据序列转换并同时计算数据

    问题 你需要在数据序列上执行聚集函数(比如 sum() , min() , max() ), 但是首先你需要先转换或者过滤数据 解决方案 一个非常优雅的方式去结合数据计算与转换就是使用一个生成器表达式 ...

  4. 4、服务注册&服务提供者

    1.什么是服务提供者 服务提供者(Service Provider):是指服务的被调用方(即:为其它服务提供服务的服务):服务提供者,作为一个Eureka Client,向Eureka Server做 ...

  5. Jmeter服务器性能压测-用户登录实例CSV方式

    为什么用CSV方式压测,因为用jdbc链接数据库,我发现数据库数据量量大的情况下,Jmeter会内存溢出 第一步:数据准备,根据登录接口需要的参数准备测试数据 例子中,测试的登录接口需要4个参数化数据 ...

  6. 项目案例之GitLab的数据迁移

    项目案例之GitLab的数据迁移 链接:https://pan.baidu.com/s/1CgaEv12cwfbs5RxcNpxdAg 提取码:fytm 复制这段内容后打开百度网盘手机App,操作更方 ...

  7. 【学术篇】CF932E Team Work && bzoj5093 图的价值

    两个题的传送门 对于CF这道题, 分别考虑每种可能的集合大小, 每个大小为\(k\)的集合数量有\(\binom nk\)个, 所以最后的答案就是 \[\sum_{i=0}^n\binom{n}{i} ...

  8. Java数据库事务四大特性以及隔离级别

    四大特性ACID 原子性(Atomicity) 原子性是指事务包含的所有操作要么全部成功,要么全部失败回滚.失败回滚的操作事务,将不能对数据库有任何影响 一致性(Consistency) 一致性是指事 ...

  9. magento结构解析

    Magento 模块 模块( module )是 Magento 的核心.站点上的任何一个动作( action ),无论是在前台和还是在后台的每一个操作都是通过模块来实现的.模块是可以视为一个容器,它 ...

  10. maven命令行创建项目问题

    今天在命令行下创建maven项目,使用的是create命令,但是一直失败,网上查找原因说archetype:create命令已经过期,需要使用 archetype:generate 来进行代替 加上了 ...