单元测试框架--unittest使用笔记

unittest

  • 单元测试框架简介
  • unittest初识
    • 自己编写测试代码
    • 使用unittest单元测试框架
    • unittest中的重要概念
      • Test Case
      • Test Suite
      • Test Runner
      • Test Fixture
    • 断言方法
    • 测试用例的组织与discover方法
  • unittest深入
    • unittest用例执行顺序
    • 执行多级目录的测试用例
    • 跳过测试和预期失败
    • Fixture
    • (扩展)生成HTML测试报告
      • `HTMLTestRunner.py`文件

单元测试框架简介

单元测试本质是通过一段代码去验证另外一段代码。单元测试框架不仅可以用来做单元测试,它还适用于不同类型的“自动化”测试
单元测试框架功能如下:

  1. 提供用例组织和执行
  2. 提供丰富的断言方法
  3. 提供丰富的日志

unittest初识

创建一个计算器类:

# 计算器类
class Calculator(object):
    """用于完成两个数的加、减、乘、除"""

    def __init__(self, a, b):
        self._a = a
        self._b = b

    # 加法
    def add(self):
        return self._a + self._b

    # 减法
    def sub(self):
        return self._a - self._b

    # 乘法
    def mul(self):
        return self._a * self._b

    # 除法
    def div(self):
        return self._a / self._b

自己编写测试代码

传统测试,是在测试代码中,首先引入calculator.py文件中国的Calculator类,并对测试数据进行初始化,接下来调用该类下面的方法,得到结果,使用assert断言是否正确。

from calculator import Calculator


def test_add():
    c = Calculator(3, 5)
    result = c.add()
    assert result == 3


if __name__ == "__main__":
    test_add()

但这样的的测试存在着一些问题,首先,需要自己定义断言失败的提示;其次,当一个测试函数运行失败时,后面的测试函数不在执行;最后,执行结果无法统计。

使用unittest单元测试框架

from calculator import Calculator
import unittest


class TestCalculator1(unittest.TestCase):

    # 测试加法
    def test_add(self):
        c = Calculator(3, 5)
        result = c.add()
        self.assertEqual(result, 8)

    # 测试减法
    def test_sub(self):
        c = Calculator(5, 2)
        result = c.sub()
        self.assertEqual(result, 4)

    # 测试乘法
    def test_mul(self):
        c = Calculator(3, 4)
        result = c.mul()
        self.assertEqual(result, 12)


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

执行结果
单元测试框架--unittest使用笔记_第1张图片

  • .(success): 表示测试用例运行通过
  • F(failed):表示测试用例运行失败
  • E(error): 表示测试脚本中存在错误

引入unittest模块。如果想用unittest编写测试用例。必须遵守它的“规则”。

  1. 创建一个测试类,继承unittest模块的TestCase类。
  2. 创建一个测试方法,该方法必须以【test】开头。
  3. 调用测试类,传入初始化数据,调用测试方法,通过unittest提供的断言方法断言是否与预期结果相同。
  4. 通过unittest的main()来执行测试用例,它会按照前面的两条规划查找测试用例并执行。

unittest中的重要概念

在unittest文档中有四个重要概念:TestCase、Test Suite、Test Runner和Test Fixture

Test Case

它是最小的测试单元,用于检查特定输入集合的特定返回值。unittest提供了TestCase基类,我们创建的测试类需要继承该基类,他可以用来创建新的测试用例。

Test Suite

测试套件是测试用例、测试套件或两者的集合,用于组装一组要运行的测试。unittest提供了TestSuite类来创建测试套件。

Test Runner

Test Runner是一个组件,用于协调测试的执行并向用户提供结果。Test Runner可以使用图形界面、文本界面或返回特殊值来展示执行测试的结果。unittest提供TextTestRunner类来运行测试用例,为了生成HTML格式的测试报告,可以选择使用第三方运行类(HTMLTestRunner)。

Test Fixture

Test Fixture代表执行一个或多个测试所需要的环境准备,以及关联的清理动作,例如,创建临时或代理数据库、目录,或启动服务器进程。unittest中提供了setUp()/tearDown()、setUpClass()/tearDownClass()等方法来完成这些操作。

import unittest
from calculator import Calculator


class TestCalculator2(unittest.TestCase):

    # 测试用例前置动作
    def setUp(self):
        print("Test Start---")

    # 测试用例后置动作
    def tearDown(self):
        print("Test end----")

    # 测试加法
    def test_add(self):
        c = Calculator(3, 5)
        result = c.add()
        self.assertEqual(result, 8)

    # 测试减法
    def test_sub(self):
        c = Calculator(5, 2)
        result = c.sub()
        self.assertEqual(result, 4)

    # 测试乘法
    def test_mul(self):
        c = Calculator(3, 4)
        result = c.mul()
        self.assertEqual(result, 12)


if __name__ == "__main__":
    # 创建测试套件
    suite = unittest.TestSuite()
    suite.addTest(TestCalculator2("test_add"))
    suite.addTest(TestCalculator2("test_sub"))
    suite.addTest(TestCalculator2("test_mul"))

    # 创建测试运行器
    runner = unittest.TextTestRunner()
    runner.run(suite)

断言方法

在执行测试用例过程中,最终测试用例执行成功与否,是通过测试得到的实际结果与预期结果进行比较得到的。unittest框架的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 is None
assertIsNotNone(x) x is not None
assertIn(a, b) a in b
assertNotIn a not in b
assertIsInstance(a, b) isinstance(a, b)
assertNotIsInstance(a, b) not isinstance(a,b)

测试用例的组织与discover方法

一个功能对应一条测试用例显然是不够的,要写多少测试用例取决于你对功能需求与测试方法的理解。对于测试用例的划分,建议一个测试类对应一个被测试的功能。

import unittest
from calculator import Calculator


class TestAdd(unittest.TestCase):
    """add() 方法测试"""

    def test_add_integer(self):
        """整数相加"""
        c = Calculator(3, 5)
        result = c.add()
        self.assertEqual(result, 8)

    def test_add_decimal(self):
        """小数相加"""
        c = Calculator(3.2, 5.3)
        self.assertEqual(c.add(), 8.5)

    def test_add_string(self):
        """字符串相加"""
        c = Calculator("hello", "world")
        self.assertEqual(c.add(), "hello world")


class TestSub(unittest.TestCase):
    """sub() 方法测试类"""

    pass


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

如何执行多个测试文件?unittest中的TestLoader类提供的discover()方法可以从多个文件中查找测试用例。
该类根据各种标准加载测试用例,并将它们返回给测试套件。正常情况下,不需要创建这个类的实例。unittest提供了可以共享的defaultTestLoader类,可以使用其子类或方法创建实例,discover方法就是其中之一

discover(start_dir, pattern="test*.py", top_level_dir=None)

找到指定目录及其子目录下的所有测试模块,只有匹配的文件名才能被加载。如果启动的不是顶层目录,那梦顶层目录必须单独指定。

  • start_dir:待测试的模块名或测试用例目录
  • pattern=“test*.py”:测试用例文件名的匹配原则。此处匹配文件名以【test】开头的【.py】文件,星号代表任意多个字符
  • top_level_dir=None:测试模块的顶层目录,如果没有,则默认为None

之后通过discover方法重新实现run_tests.py文件的功能

import unittest

# 定义测试用例的目录为当前目录
test_dir = "./"
suits = unittest.defaultTestLoader.discover(test_dir, pattern="test*.py")


if __name__ == "__main__":
    runner = unittest.TextTestRunner()
    runner.run(suits)

unittest深入

unittest用例执行顺序

unittest默认根据ASCII码的顺序加载测试用例,所以TestAdd类会优先TestBdd类先执行,test_add()方法会优先test_bdd()方法执行。在discover()和main()方法中,也是如此,test_aaa.py文件会优先test_bbb.py执行。并不是按照测试用例的创建顺序从上到下执行的。因此,如果想让某个测试文件先执行,可以在命名上加以控制。

import unittest


class TestAdd(unittest.TestCase):

    def setUp(self):
        print("test TestAdd")

    def test_ccc(self):
        print("test ccc")

    def test_aaa(self):
        print("test aaa")


class TestBdd(unittest.TestCase):

    def setUp(self):
        print("test TestBdd")

    def test_bbb(self):
        print("test bbb")


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

执行结果

test TestAdd
test aaa
test TestAdd
test ccc
test TestBdd
test bbb

除命名外,还可以声明测试套件TestSuite类,通过addTest()方法按照一定的顺序来加载测试用例

if __name__ == "__main__":
    suites = unittest.TestSuite()
    suites.addTest(TestBdd("test_bbb"))
    suites.addTest(TestAdd("test_aaa"))
    suites.addTest(TestAdd("test_ccc"))

    runner = unittest.TextTestRunner(verbosity=3)
    runner.run(suites)

执行结果

test_bbb (__main__.TestBdd) ... ok
test_aaa (__main__.TestAdd) ... ok
test_ccc (__main__.TestAdd) ... ok

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

OK
test TestBdd
test bbb
test TestAdd
test aaa
test TestAdd
test ccc

注意:在pycharm中,引入了unittest模块,会默认按照unittest模式执行。需要将unittest模式转换成普通模式。
unittest模式转换成普通模式的方法:https://blog.csdn.net/qq_33356414/article/details/94556140

执行多级目录的测试用例

当测试用例的数量达到一定量级,需要考虑目录的划分,如下:
单元测试框架--unittest使用笔记_第2张图片
对于以上目录结果,如果将discover方法中的start_dir参数定义为【./test_case】目录,那只能加载【test_a.py】文件的测试用例,解决办法是需要在每个目录下放一个【__init__.py】文件,将一个目录标记成一个标准的Python模块。

跳过测试和预期失败

运行测试时,有时需要直接跳过某些测试用例,或者当测试用例符合某个条件时跳过测试,又或者直接将测试用例摄者为失败。unittest提供了实现这些需求的装饰器

  • unittest.skip(reson): 无条件跳过装饰的测试,需要说明跳过原因
  • unittest.skipIf(condition, reason): 满足条件,则跳过装饰的测试
  • unittest.skipUnless(condition, reason): 满足条件,执行装饰的测试
  • unittest.expectedFailure(): 不管执行结果是否失败,都将测试标记为失败
import unittest


class MyTest(unittest.TestCase):

    @unittest.skip("直接跳过测试")
    def test_skip(self):
        print("test skp")

    @unittest.skipIf(3 > 2, "if true, skip case")
    def test_ifskip(self):
        print("test if skip")

    @unittest.skipUnless(3 > 2, "if true, run case")
    def test_unless(self):
        print("test unless test")

    @unittest.expectedFailure
    def test_expectedFailuer(self):
        print("hahah")


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

  1. 第一条测试用例直接通过@unittest.skip()装饰,跳过测试
  2. 第二条测试用例通过@unittest.skipIf(3 > 2, "if true, skip case"),条件为真,跳过测试
  3. 第三条测试用例通过@unittest.skipUnless(3 > 2, "if true, run case"),条件为真,执行测试
  4. 第四条测试,不管执行结果是否失败,都将测试标记为失败,但不会抛出失败信息。

同时,这些方法同样适用于测试类。

Fixture

Fixture可看做夹心饼干外层的两片饼干,就是setUp/tearDown,中间的奶油就是测试用例。unittest还提供了更大范围的Fixture,如测试类和模块的Fixture

  • setUpModule/tearDownModule: 整个模块的开始于结束时被执行
  • setUpClass/tearDownClass : 在测试类的开始与结束时被执行
  • setUp/tearDown: 在测试用以的开始与结束时被执行
import unittest


def setUpModule():
    print("test module start >>>>>>>>>>>>>>>")

def tearDownModule():
    print("test module end <<<<<<<<<<<<<<<<<<<")


class MyTest(unittest.TestCase):

    @classmethod
    def setUpClass(cls):
        print("test class start >>>>>>>>>>")

    @classmethod
    def tearDownClass(cls):
        print("test class end <<<<<<<<<")

    def setUp(self):
        print("test case start >>>>")

    def tearDown(self):
        print("test case end <<<<")

    def test_case1(self):
        print("test case1")

    def test_case2(self):
        print("test case2")


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

执行结果

Testing started at 下午 14:55 ...
D:\ENV_DIRECTOR\Envs\py3_heima\Scripts\python.exe "F:\Pycharm\PyCharm 2017.3.3\helpers\pycharm\_jb_unittest_runner.py" --target test_fixture.MyTest
Launching unittests with arguments python -m unittest test_fixture.MyTest in D:\zhenghou\python_learning\python_test
test module start >>>>>>>>>>>>>>>
test class start >>>>>>>>>>test case start >>>>
test case1
test case end <<<<
test case start >>>>
test case2
test case end <<<<
test class end <<<<<<<<<
test module end <<<<<<<<<<<<<<<<<<<

(扩展)生成HTML测试报告

unittest单元测试框架可以将测试结果写到测试报告中,通过测试报告可以清晰地查看自动化测试用例总数、通过数、失败数以及失败原因等信息。

HTMLTestRunner.py文件

可以将HTMLTestR.py文件下载:【HTMLTestRunner】,
此前,测试用例执行是通过【TextTestRunner】类提供的run()方法完成的。这里需要把【HTMLTestRunner.py】文件中的HTMLTestRunner类替换TextTestRunner类。
单元测试框架--unittest使用笔记_第3张图片
建议将该py文件直接放在项目目录下。使用时直接导入
如图:
单元测试框架--unittest使用笔记_第4张图片
该项目下有5个test开头的测试文件,执行里面的测试用例,并生成HTML格式的测试报告。

import os
import time
import unittest

from HTMLTestRunner import HTMLTestRunner




def getAllCase():
    """获取目录下所有的test case"""
    suits = unittest.defaultTestLoader.discover(start_dir=os.path.abspath(os.path.dirname(__file__)), pattern="test*.py")
    return suits


def Runmain():
    """生成测试报告"""
    time_str = time.strftime("%Y%m%d%H%M%S")
    f = open(os.path.join(os.path.dirname(__file__), "report", time_str + "report.html"), "wb")

    runner = HTMLTestRunner(stream=f, title="unittest自动化测试报告", description="基于Python-Unittest的单元测试自动化")
    runner.run(getAllCase())


if __name__ == '__main__':
    Runmain()

部分参数解释:

  • stream: 指定生成HTML测试报告的文件,必填
  • verbosity:指定日志的级别,默认为1.如果想得到更详细的日志,则可以将参数修改为2
  • title:指定测试用例的标题,默认为None
  • description:指定测试用例的描述,默认为None

生成测试报告:
单元测试框架--unittest使用笔记_第5张图片

你可能感兴趣的:(自动化测试,单元测试框架)