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. datepicker时间选择弹窗

    准备工作 首先请到jqueryui.com官网下载datepicker插件代码,注意官网提供了整个jquery ui的所有插件下载,但是您可以选择其中几个用到的插件下载,本文中只用到datepicke ...

  2. java中传参的要点

    传参的是分为:值传递和引用传递,值传递不会改变值,传递的是这个值的副本(就是把这个值拿出来拷贝一份用拷贝的值):引用传递会改变值,传递的是这个值的“地址”: String是特殊的引用类型,用Strin ...

  3. 【目录】ASP.NET Core 基础教程

    ASP.NET Core 基础教程 ASP.NET Core 基础教程 ASP.NET Core 简介 ASP.NET Core Windows 环境配置 ASP.NET Core macOS 环境配 ...

  4. 解决code first Migration 增加外键时出现错误的问题

    先上模型 Comment public class Comment { [Key] public int CommentId { get; set; } [Required] public int S ...

  5. 寻找链表倒数第k个元素,只遍历一遍(编程之美)

    class LNode { public LNode next; public int data; } /*找出倒数第k个元素,只遍历一遍*/ class Kk { private static LN ...

  6. 创建win32 dll 空项目

    动态库,多字节 win32 空项目 添加导出头文件  类 导入: #pragma once #ifndef IP_CLASS_DLL_H #define IP_CLASS_DLL_H #pragma ...

  7. java反射的使用场合和作用、及其优缺点

    1)使用场合 在编译时根本无法知道该对象或类可能属于哪些类,程序只依靠运行时信息来发现该对象和类的真实信息. 2)主要作用 通过反射可以使程序代码访问装载到JVM 中的类的内部信息,获取已装载类的属性 ...

  8. MySQL数据库操作:“增删改查”,忘记密码重置等。

    [注] 数据库的“增删查改”,参考原作者Wid:http://www.cnblogs.com/mr-wid/archive/2013/05/09/3068229.html#d11.感谢大佬们的技术分享 ...

  9. sql 合并查询结果

    在使用js报表工具的时候,常常需要提供json数据进行显示, 在sql查询的时候就需要构造合适的查询结果出来; 就用到了合并两个没有关联关系的表数据, SELECT SUM(a1.amount) AS ...

  10. springboot集成使用rabbitmq笔记(3.基本过程)

    使用rabbitmq笔记一 使用rabbitmq笔记二 使用rabbitmq笔记三 1.AMQP协议 AMQP 0-9-1的工作过程如下图:消息(message)被发布者(publisher)发送给交 ...