每日一包 - unitest

140 阅读6分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第17天,点击查看活动详情

unitest是Python内置模块,帮助代码实现单元测试,并生成测试报告。

基本使用

# 使用注意点
# 1. 测试类必须继承unitest.TestCase
# 2. 在测试类中的每个方法或者函数如果是需要被执行的,方法名或者函数名必须以test开头import unittest
​
​
def my_sum(a, b):
    return a + b
​
​
class MyTestCase(unittest.TestCase):
​
    @staticmethod
    def test01():
        print(my_sum(1, 2))
​
    @staticmethod
    def test02():
        print(my_sum(3, 4))

testsuite使用

在实际工作中,将所有的测试用例都放在同一个类下面是不切合实际的,可以借助testsuite将测试用例从其他文件中加载过来,然后执行,还需要借助TextTestRunner运行这些测试用例。

import unittest
import testcase
​
suite = unittest.TestSuite()  # 生成测试套件,可以理解为装测试用例的容器
suite.addTest(testcase.MyTestCase("test01"))  # 向容器中添加测试用例
suite.addTest(testcase.MyTestCase("test02"))
​
runner = unittest.TextTestRunner()   # 生成运行测试用例的工具
runner.run(suite)  # 运行测试用例

使用上述方式虽然解决了将其他文件中的测试用例一起执行的问题,但是如果有非常多的测试用例,每次都通过addTest方法添加会出现大量重复代码,可以借助makeSuite方法将测试用例以类为单位添加到测试套件中,这种方式相对于addTest会节省很多代码。

import unittest
import testcase
​
​
suite = unittest.makeSuite(testCaseClass=testcase.MyTestCase)  
runner = unittest.TextTestRunner()   # 生成运行测试用例的工具
​
​
runner.run(suite)  # 运行测试用例

说了这么多,以上方法都是不常用的!!!

常用的是TestLoader(),可以将测试用例以文件为单位加入到套件中,使用方式也更加简单:

import unittest
import testcase
​
​
suite = unittest.TestLoader().discover(r"./", "test*.py")   # 生成测试套件,装测试用例的容器,并且加载py文件中的测试用例,意思是将当前目录下以test开头的py文件中只的所有测试用例都加载到测试套件中
runner = unittest.TextTestRunner()   # 生成运行测试用例的工具
runner.run(suite)  # 运行测试用例

TestSuite和TestLoader的使用区别

  • 当只是要执行py文件中多个测试用例中的几个,而不是全部执行那么适合用TestSuite的addTest加载指定的测试用例
  • 当要执行所有的py文件中的所有的测试用例,那么适合使用TestLoader

Fixture

  • 可以在测试用例执行执行之前自动调用指定的函数,在测试用例执行之后自动调用指定的函数

  • 控制级别

    • 方法级

      • 每个方法执行前和执行后都自动调用函数
    • 类级

      • 不管类中有多少方法,一个类执行前后都自动调用函数
    • 模块级

      • 不管一个模块(一个模块就是一个py文件)中有多少类,模块执行前后自动调用函数

方法级

  • 在TestCase,也就是测试用例所在的class中定义方法
  • def setUp(self) 当测试用例执行前,自动被调用
  • def tearDown(self) 当测试用例执行后,自动被调用
  • 如果一个TestCase中有多个测试用例,那么setUp和tearDown就会被自动调用多次

mytest.py内容修改如下:

import unittest
​
def my_sum(a, b):
    return a + b
​
class my_test(unittest.TestCase):
    def setUp(self):
        print("setup被自动调用了")
    def tearDown(self):
        print("teardown被自动调用了")
​
    def test_001(self):
        print(my_sum(5, 6))
​
    def test_002(self):
        print(my_sum(0, 3))

类级

  • 不管类中有多少方法,一个类开始的时候自动调用函数,结束的之后自动调用函数
  • 类级的fixture一定要是类方法
  • @classmethod def setUpClass(cls)类开始时自动调用的方法
  • @clasmethod def tearDownClass(cls)类结束的时候自动调用的方法

mytest.py修改如下:

import unittest
​
def my_sum(a, b):
    return a + b
​
class my_test(unittest.TestCase):
    @classmethod
    def setUpClass(cls):
        print("setupclass自动调用了")
    @classmethod
    def tearDownClass(cls):
        print("teardownclass自动调用了")
    def setUp(self):
        print("setup被自动调用了")
    def tearDown(self):
        print("teardown被自动调用了")
​
    def test_001(self):
        print(my_sum(5, 6))
​
    def test_002(self):
        print(my_sum(0, 3))
​
​
​

模块级

  • 不管py文件中有多少个类,以及类中有多少方法,只自动执行一次
  • def setUpModule() 在py文件开始的时候自动调用
  • def tearDownModule() 在py文件结束的时候自动调用

修改后的mytest.py内容如下

import unittest
​
def setUpModule():
    print("setUpModule自动调用了")
​
def tearDownModule():
    print("tearDownModule自动调用了")
​
def my_sum(a, b):
    return a + b
​
class my_test1(unittest.TestCase):
    @classmethod
    def setUpClass(cls):
        print("setupclass自动调用了")
    @classmethod
    def tearDownClass(cls):
        print("teardownclass自动调用了")
    def setUp(self):
        print("setup被自动调用了")
    def tearDown(self):
        print("teardown被自动调用了")
​
    def test_001(self):
        print(my_sum(5, 6))
​
    def test_002(self):
        print(my_sum(0, 3))
​
class my_test2(unittest.TestCase):
    @classmethod
    def setUpClass(cls):
        print("setupclass自动调用了")
    @classmethod
    def tearDownClass(cls):
        print("teardownclass自动调用了")
    def setUp(self):
        print("setup被自动调用了")
    def tearDown(self):
        print("teardown被自动调用了")
​
    def test_001(self):
        print(my_sum(5, 6))
​
    def test_002(self):
        print(my_sum(0, 3))

fixture小结

  • 一定要在继承于unittest.TestCase这个类的子类中使用
  • setUP,tearDown, 每个方法执行开始和完毕后自动调用
  • setUPClass, tearDownClass, 每个类开始时候和结束时候自动调用
  • setUpModule, tearDownModule,每个py文件开始和结束的时候自动调用

断言

断言的方法在unitest.TestCase()中已经定义好,因此在测试类中的方法直接使用即可。

import unittest
​
​
def my_sum(a, b):
    return a + b
​
​
class MyTestCase(unittest.TestCase):
​
    def test01(self):
        self.assertEquals(10, my_sum(5, 5))

参数化

多个测试用例代码相同,只是测试数据不同,预期结果不同,可以把多个测试用例通过参数化技术合并为一个测试用例,这个时候我们就可以使用参数化实现这种需求。

使用参数化需要记住第三方包parameterized,使用方式如下

import unittest
from parameterized import parameterized


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


class MyTestCase(unittest.TestCase):
    # 使用装饰器将不同的组合值写入到列表中,列表中的元组放的是不同的组合
    # 执行时将列表打散得到不同的元组,再将元组中的值打散传递给测试方法
    @parameterized.expand([(1, 1, 2), (2, 2, 4)])
    def test01(self, a, b, c):
        """
        :param a: first param
        :param b: second param
        :param c: result
        :return: None
        """
        self.assertEquals(c, my_sum(a, b))

跳过某个测试用例

# 不希望执行某个测试用例的时候可以使用
class MyTestCase(unittest.TestCase):

    @unittest.skip("不想执行")
    # @unittest.skipIf(True, "没写好")  # @unitest.skipIf(condition, reason)  当condition为True时,返回reason
    def test02(self):
        self.assertEquals(1, my_sum(1, 1))

生成测试报告

TextTestRunner(比较丑很少使用)

import unittest


suite = unittest.TestLoader().discover(r"./", "testc*.py")   # 生成测试套件,装测试用例的容器,并且加载py文件中的测试用例
with open("test.txt", "w", encoding="utf8") as f:
    runner = unittest.TextTestRunner(stream=f, verbosity=2)   # 生成运行测试用例的工具,将产生的测试用例结果写到文件中
    runner.run(suite)  # 运行测试用例

HtmlTestRunner

import unittest
from HtmlTestRunner import HTMLTestRunner


suite = unittest.TestLoader().discover(r"./", "testc*.py")   # 生成测试套件,装测试用例的容器,并且加载py文件中的测试用例
with open("test.txt", "w", encoding="utf8") as f:
    runner = HTMLTestRunner(stream=f, report_title="test.html")   # 生成运行测试用例的工具,将产生的测试用例结果写到文件中
    runner.run(suite)  # 运行测试用例

\