Python 测试相关

软件开发固然重要,软件测试也必不可少。

一. Python 文档测试

GitHub代码commits id:c5bdcc5

1. pydoc生成文档

python 的 pydoc 模块可以非常方便地查看、生成 HTML 帮助文档。只要在函数、类、方法定义后面加

#!/usr/bin/env python
# -*- encoding: utf-8 -*-
"""
@File    :   01_create_pydoc.py
@Time    :   2019/8/16 11:36
@Author  :   Crisimple
@Github :    https://crisimple.github.io/
@Contact :   Crisimple@foxmail.com
@License :   (C)Copyright 2017-2019, Micro-Circle
@Desc    :   None
"""
 NMAE = 'PyDOC'

class CreatePyDOC(object):
    name = 'pydoc'
    """
    定义一个CreatePyDOC,该类包括两个变量:name、language
    """
    def __int__(self, name, language):
        """
        name --- 初始化文档的名称
        language --- 初始化文档的编写语言
        :param name:
        :param language:
        :return:
        """
        self.name = name
        self.age = language

    def print_info(self, language):
        """
        定义一个print_info方法
        language代表使用的语言
        :param language:
        :return:
        """
        print("%s doc 是用 %s 编写的" % (self.name, language))


def create_doc(name, language, money):
    """
    定义一个打印创建doc信息的函数
    name --- 档的称
    language --- 文档语言
    money --- 售价
    :param name:
    :param language:
    :param money:
    :return:
    """
    print("doc的名字是 %s, 它的语言是 %s, 售价 %d"% (name, language, money))

1.1 pydoc 在控制台中查看文档

(1) 模块的文档说明:就是*.py 文件顶部的注释信息,这部分信息会被提取成模块的文档说明。
(2) CLASSES 部分:这部分会列出该模块所包含的全部类。
(3) FUNCTIONS 部分:这部分会列出该模块所包含的全部函数。
(4) DATA 部分:这部分会列出该模块所包含的全部成员变量。
(5) FILE 部分:这部分会显示该模块对应的源文件。
# m 是 python 命令的一个选项,表示运行指定模块
python -m pydoc 01_create_pydoc


# *********执行结果*************
$ python -m pydoc 01_create_pydoc
Help on module 01_create_pydoc:

NAME
    01_create_pydoc

DESCRIPTION
    @File    :   01_create_pydoc.py
    @Time    :   2019/8/16 11:36
    @Author  :   Crisimple
    @Github :    https://crisimple.github.io/
    @Contact :   Crisimple@foxmail.com
    @License :   (C)Copyright 2017-2019, Micro-Circle
    @Desc    :   None

CLASSES
    builtins.object
        CreatePyDOC

    class CreatePyDOC(builtins.object)
     |  Methods defined here:
     |
     |  __int__(self, name, language)
     |      name --- 初始化文档的名称
     |      language --- 初始化文档的编写语言
     |      :param name:
     |      :param language:
     |      :return:
     |
     |  print_info(self, language)
     |      定义一个print_info方法
     |      language代表使用的语言
     |      :param language:
     |      :return:
     |
     |  ----------------------------------------------------------------------
     |  Data descriptors defined here:
     |
     |  __dict__
     |      dictionary for instance variables (if defined)
     |
     |  __weakref__
     |      list of weak references to the object (if defined)
     |
     |  ----------------------------------------------------------------------
     |  Data and other attributes defined here:
     |
     |  name = 'pydoc'

FUNCTIONS
    create_doc(name, language, money)
        定义一个打印创建doc信息的函数
        name --- 档的称
        language --- 文档语言
        money --- 售价
        :param name:
        :param language:
        :param money:
        :return:

DATA
    NAME = 'PyDOC'

FILE
    d:\myspace\python\unittest\doc\01_create_pydoc.py

1.2 pydoc 生成 HTML 文档

# -w 选项,该选项代表 write,表明输出 HTML 文档
python -m pydoc -w 01_create_pydoc

pydoc 生成 HTML 文档

1.3 启动本地服务查看文档

第一部分显示 Python 内置的核心模块。
第二部分显示 当前目录下的所有模块。
第三部分显示 目录下的所有模块,此时在该目录下并未包含任何模块。第三部分用于显示 PYTHONPATH 环境变量所指定路径下的模块
python -m pydoc -p 1234

# 输出信息
python -m pydoc -p 1234
Server ready at http://localhost:1234/
Server commands: [b]rowser, [q]uit
server> b    --- 启动浏览器,当然也可以自己在浏览器输入地址
server> q    --- 退出本地服务
Server stopped

1.4 pydoc 查找模块

-k 后跟被搜索模块的部分内容,即可以进行模糊搜索

python -m pydoc -k create

2. doctest 文档测试

  文档测试,即通过 doctest 模块运行 Python 源文件的说明文档中的测试用例,从而生成测试报告。

  文档测试工具可以提取文档说明中的测试用例,">>>" 后面就表示的是测试用例,紧接着的一行则代表测试用例的输出结果(即预期结果)。文档测试工具会判断测试用例与预期结果是否一致,不一致会输出错误信息。
GitHub代码commits id:cda157d

#!/usr/bin/env python
# -*- encoding: utf-8 -*-
"""
@File    :   02_doctest.py
@Time    :   2019/8/17 15:48
@Author  :   Crisimple
@Github :    https://crisimple.github.io/
@Contact :   Crisimple@foxmail.com
@License :   (C)Copyright 2017-2019, Micro-Circle
@Desc    :   None
"""

import doctest


class User:
    """
    定义一个代表用户的类,该类包含两个属性:
    name - 用户的名字
    age - 用户的年龄

    >>> u = User('abc', 9)
    >>> u.name
    'ABC'
    >>> u.age
    9
    >>> u.say()
    'abc 说,我今年 10 岁了。'
    """
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def say(self):
        return "%s 说,我今年 %s 岁了。" % (self.name, self.age)


if __name__ == "__main__":
    # testmod() 函数会自动提取该模块的说明文档中的测试用例,并执行这些测试用例,最终生成测试报告。如果存在没有通过的测试用例,程序就会显示有多少个测试用例没有通过;如果所有测试用例都能通过测试,则不生成任何输出结果。
    doctest.testmod()

执行以后输出的测试执行报告

Failure
<Click to see difference>

**********************************************************************
File "C:/MySpace/Python/UnitTest/doc/02_doctest.py", line 23, in User
Failed example:
    u.name
Expected:
    'ABC'
Got:
    'abc'

Failure
<Click to see difference>

**********************************************************************
File "C:/MySpace/Python/UnitTest/doc/02_doctest.py", line 27, in User
Failed example:
    u.say()
Expected:
    'abc 说,我今年 10 岁了。'
Got:
    'abc 说,我今年 9 岁了。'

Process finished with exit code 0
第一部分:显示在哪个源文件的哪一行。
第二部分:Failed example,显示是哪个测试用例出错了。
第三部分:Expected,显示程序期望的输出结果。也就是在“>>>命令”的下一行给出的运行结果,它就是期望结果。
第四部分:Got,显示程序实际运行产生的输出结果。只有当实际运行产生的输出结果与期望结果一致时,才表明该测试用例通过。

二、Python 单元测试

1. PyUnit(unittest)

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

PyUnit的特点:

(1)让测试具有持久性,测试与开发同步进行,测试代码与开发代码一并发布;
(2)可以是测试代码与产品代码分离;
(3)针对某一个类的测试代码只需少量改动就可以应用与另一个类的测试;
(4)使用断言方法判断期望值与实际值的差异,返回bool值;
(5)测试驱动设备可以使用共同的处理化变量或实例;
(6)测试包结构便于组织集成运行。

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

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

当测试是不想执行某一条测试用例,除了注释测试用例相关代码外,还可以进行如下操作跳过测试用例的执行:

GitHub代码commits id:56a251f

#!/usr/bin/env python
# -*- encoding: utf-8 -*-
"""
@File    :   03_pyunit.py
@Time    :   2019/8/17 17:21
@Author  :   Crisimple
@Github :    https://crisimple.github.io/
@Contact :   Crisimple@foxmail.com
@License :   (C)Copyright 2017-2019, Micro-Circle
@Desc    :   None
"""

import unittest


def one_equation(a, b):
    """
    求解一元一次方程 a * x + b = 0 的解
    a --- 方程中变量的系数
    b --- 方程中的产量
    返回方程的解
    :param a:
    :param b:
    :return:
    """
    # 方程无解
    if a == 0:
        raise ValueError("参数错误,分母不能为0")
    # 返回方程的解
    else:
        return b / a


def two_equation(a, b, c):
    """
    求解一元二次方程 a * a * x + b * x + c = 0
    a --- 方程中变量二次幂的系数
    b --- 方程中变量一次幂的系统
    c --- 方程中的常量
    :param a:
    :param b:
    :param c:
    :return:
    """
    if a == 0:
        raise ValueError("参数错误")
    elif b * b - 4 * a * c < 0:
        raise ValueError("方程在有理数范围内无解")
    elif b * b - 4 * a * c == 0:
        return -b / (2 * a)
    else:
        r1 = (-b + (b * b - 4 * a * c) ** 0.5) / 2 / a
        r2 = (-b - (b * b - 4 * a * c) ** 0.5) / 2 / a
        return r1, r2


class TestMath(unittest.TestCase):
    # 测试一元一次方程的求解
    def test_one_equation(self):
        self.assertEqual(one_equation(2, 5), 2.5)

        self.assertTrue(one_equation(4, -28) == -7)

        with self.assertRaises(ValueError):
            one_equation(0, 7)

    # 测试一元二次方程的求解
    def test_two_equation(self):
        r1, r2 = two_equation(1, -3, 2)
        self.assertCountEqual((r1, r2), (1.0, 2.0), '求解出错')
        r1, r2 = two_equation(2, -7, 6)
        self.assertCountEqual((r1, r2), (1.5, 2.0), '求解出错')
        # 断言只有一个解的情形
        r = two_equation(1, -4, 4)
        self.assertEqual(r, 2.0, '求解出错')
        # 断言当a == 0时的情况,断言引发ValueError
        with self.assertRaises(ValueError):
            two_equation(0, 9, 3)
        # 断言引发ValueError
        with self.assertRaises(ValueError):
            two_equation(4, 2, 3)


if __name__ == '__main__':
    unittest.main()
#  -v 选项来生成更详细的测试报告
$ python -m unittest -v 03_pyunit.py
test_one_equation (03_pyunit.TestMath) ... FAIL
test_two_equation (03_pyunit.TestMath) ... skipped '临时跳过该测试用例不执行'

======================================================================
FAIL: test_one_equation (03_pyunit.TestMath)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "C:\MySpace\Python\UnitTest\doc\03_pyunit.py", line 60, in test_one_equation
    self.assertEqual(one_equation(2, 5), 1.5)
AssertionError: 2.5 != 1.5

----------------------------------------------------------------------
Ran 2 tests in 0.003s

FAILED (failures=1, skipped=1)


测试结果的输出结果含义:

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

1.1 常用断言方法

unittest.TestCase 内置大量的 assertXXX 方法来执行断言,常用的断言方法有:

断言方法检查条件
assertEqual(a, b)a == b
assertNotEqual(a, b)100
assertTrue(x)bool(x) is True
assertFalse(x)bool(x) is False
assertIs(a, b)a is b
assertNot(a, b)a is not b
assertNone(x)x is None
assertNotNone(x)x is not None
assertIn(a, b)a in b
assertNotIn(a, b)a not in b
assertIsInstance(a ,b)isinstance(a, b)
assertNotIsInstance(a, b)not isinstance(a, b)

1.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 级别的日志

1.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 两个序列包含的元素相同,不管元素出现的顺序如何

1.4 针对特定类型断言

  如果被判断的类型是字符串、序列、列表、元组、集合、字典,则程序会自动改为使用下表 所示的断言方法进行判断。换而言之,下表所示的断言方法其实没有必要使用,unittest 模块会自动应用它们。

断言方法用于比较的类型
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)

2. 测试整合

2.1 测试包(TestSuite)

  测试包(TestSuite)可以组织多个测试用例,测试包还可以嵌套测试包。在使用测试包组织多个测试用例和测试包之后,程序可以使用测试运行器(TestRunner)来运行该测试用包包含的所有测试用例。

测试相关的补充:

测试用例类:测试用例类就是单个的测试单元,其负责检查特定输入和对应的输出是否匹配。unittest 提供了一个 TestCase 基类用于创建测试用例类。
测试包:用于组合多个测试用例,测试包也可以嵌套测试包。
测试运行器:负责组织、运行测试用例,并向用户呈现测试结果。
测试固件:代表执行一个或多个测试用例所需的准备工作,以及相关联的准备操作,准备工作可能包括创建临时数据库、创建目录、开启服务器进程等。

2.2 测试固件(TestFixture)

  unittest.TestCase 包含了 setUp() 和 tearDown() 两个方法,其中 setUp() 方法用于初始化测试固件;而 tearDown() 方法用于销毁测试固件。程序会在运行每个测试用例(以 test_ 开头的方法)之前自动执行 setUp() 方法来初始化测试固件,井在每个测试用例(以 test_ 开头的方法)运行完成之后自动执行 tearDown() 方法来销毁测试固件。
GitHub代码commits id:8b8e446

#!/usr/bin/env python
# -*- encoding: utf-8 -*-
"""
@File    :   04_testsuite.py
@Time    :   2019/8/18 17:43
@Author  :   Crisimple
@Github :    https://crisimple.github.io/
@Contact :   Crisimple@foxmail.com
@License :   (C)Copyright 2017-2019, Micro-Circle
@Desc    :   None
"""
import unittest


def say():
    return "Hello Python"


def func_add(a, b):
    return a + b


def func_error(a):
    return a


def func_skip():
    return "skip这个方法"


class TestSay(unittest.TestCase):
    @classmethod
    def setUpClass(cls) -> None:
        print("TestSay开始测试固件...")

    def test_say(self):
        self.assertEqual(say(), "Hello")

    @classmethod
    def tearDownClass(cls) -> None:
        print("TestSay结束测试固件...")


class TestAdd(unittest.TestCase):
    @classmethod
    def setUpClass(cls) -> None:
        print("TestAdd开始测试固件...")

    def test_func_add(self):
        self.assertEqual(func_add(1, 2), 3)
        self.assertEqual(func_add(1, -2), -1)
        self.assertEqual(func_add(-1.5, -2), -3.5)

    def test_error(self):
        self.assertEqual(func_error(2), -2)

    @unittest.skip('这条测试用例跳过不执行')
    def test_skip(self):
        self.assertEqual(func_skip(), 'skip这个方法')

    @classmethod
    def tearDownClass(cls) -> None:
        print("TestAdd结束测试固件...")


# 测试用例管理
testCases = (TestSay, TestAdd)


def whole_suite():
    # 创建测试加载器
    loader = unittest.TestLoader()

    # 创建测试包
    suite = unittest.TestSuite()

    # 遍历所有测试类
    for testCase in testCases:
        # 从测试类中加载测试用例
        tests = loader.loadTestsFromTestCase(testCase)
        # 将测试用例加到测试包中
        suite.addTests(tests)

    return suite


if __name__ == "__main__":
    with open('04_testsuite_report.txt', 'a') as f:
        # 创建测试运行器(TestRunner)
        # verbosity=2 生成更详细的测试报告
        runner = unittest.TextTestRunner(verbosity=2, stream=f)
        runner.run(whole_suite())

执行输出结果:

$ python -m unittest -v 04_testsuite.py
TestAdd开始测试固件...
test_error (04_testsuite.TestAdd) ... FAIL
test_func_add (04_testsuite.TestAdd) ... ok
test_skip (04_testsuite.TestAdd) ... skipped '这条测试用例跳过不执行'
TestAdd结束测试固件...
TestSay开始测试固件...
test_say (04_testsuite.TestSay) ... FAIL
TestSay结束测试固件...

======================================================================
FAIL: test_error (04_testsuite.TestAdd)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "C:\MySpace\Python\UnitTest\doc\04_testsuite.py", line 55, in test_error
    self.assertEqual(func_error(2), -2)
AssertionError: 2 != -2

======================================================================
FAIL: test_say (04_testsuite.TestSay)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "C:\MySpace\Python\UnitTest\doc\04_testsuite.py", line 37, in test_say
    self.assertEqual(say(), "Hello")
AssertionError: 'Hello Python' != 'Hello'
- Hello Python
+ Hello


----------------------------------------------------------------------
Ran 4 tests in 0.004s

FAILED (failures=2, skipped=1)

3.批量执行用例

  在大型项目测试中,我们会面临大量的测试用例,这些测试用例肯定不能一个个执行了,所以我们怎样来批量执行大量的测试用例呢?

  是的,用容器。我们可以将要执行的测试用例添加到容器中,执行容器达到执行多测试用例的目的。【GitHub示例

#!/usr/bin/env python
# -*- encoding: utf-8 -*-
"""
@File    :   0203_execute_more_cases.py
@Time    :   2019/8/28 22:00
@Author  :   Crisimple
@Github :    https://crisimple.github.io/
@Contact :   Crisimple@foxmail.com
@License :   (C)Copyright 2017-2019, Micro-Circle
@Desc    :   这里批量的测试用例指的是 "../testcase/ " 目录下的测试用例们
"""

import unittest
import os


class ExecuteMoreCases(unittest.TestCase):

    def test_execute(self):
        # 当前目录
        current_dir = os.path.dirname(os.path.abspath(__file__))
        print(current_dir)  # D:\MySpace\Python\WebTesting\util
        cases_dir = os.path.join('../testcase/')
        print(cases_dir)
        suite = unittest.defaultTestLoader.discover(cases_dir, '00203_case1.py')
        unittest.TextTestRunner().run(suite)


if __name__ == "__main__":
    unittest.main()
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值