单元测试----Unittest框架

落日映苍穹つ 2022-12-16 13:06 387阅读 0赞

一: Unittest核心要素:

1:核心要素概念

  • TestCase:测试用例

TestCase继承于 unittest.TestCase
测试方法必须以test开头

  • TestSuit:测试套件

测试套件: 把多个测试用例集成在一起就是测试套件。
1:实例化测试套件 suite = unittest.TestSuite()
2: 将测试用例加入测试套件 suite.addTest(MyTest(‘test_xxx’))

  • TextTestRunner:

测试执行器: 用来执行测试套件
1: 实例化测试执行器: runner = unittest.TextTestRunner()
2: 执行测试套件 : runner.run(suite)

  • FixTure:概述:

测试用例类中实现了前置和后置方法,则这个测试类就是一个Fixture。
例如实现了: setUp() 和 tearDown

2:测试流程图:

在这里插入图片描述

3: 案例演示执行流程:

  1. # 1: 导包
  2. import unittest
  3. # 2: 准备测试类
  4. class MyTest(unittest.TestCase):
  5. def test_1(self):
  6. print("这是:test_1")
  7. def test_2(self):
  8. print("这是:test_2")
  9. if __name__ == '__main__':
  10. # 3: 将测试用例添加到 测试套件中。
  11. # 实例化测试套件
  12. suite = unittest.TestSuite()
  13. # 将测试用例添加到测试套件中。
  14. suite.addTest(MyTest('test_1'))
  15. suite.addTest(MyTest('test_2'))
  16. # 4:运行容器中的测试用例
  17. # 实例化测试执行器
  18. runner = unittest.TextTestRunner()
  19. runner.run(suite)

演示:
在这里插入图片描述

4: defaultTestLoader:指定目录下测试用例自动添加到测试套件中

  1. # 1: 导包
  2. import unittest
  3. # 2: 准备测试类
  4. class MyTest(unittest.TestCase):
  5. def test_1(self):
  6. print("这是:test_1")
  7. def test_2(self):
  8. print("这是:test_2")
  9. if __name__ == '__main__':
  10. # 将当前目录下的test_1.demo.py中的测试用例全部加载到测试套件。
  11. suite = unittest.defaultTestLoader.discover("./", "test_1_demo.py")
  12. # 运行测试用例
  13. runner = unittest.TextTestRunner()
  14. runner.run(suite)

二: unittest的使用

1:unittest使用步骤

  • unittest默认加载脚本的顺序是:根据ASCII码的顺序加载,数字与字母的顺序为:0-9,A-Z,a-z

    “”” unittest的基本使用”””

    1: 导入unittest包

    import unittest

    2: 创建测试用例类

    class MyTest(unittest.TestCase):

    1. @classmethod
    2. def setUpClass(cls):
    3. print("setUpClass")
    4. @classmethod
    5. def tearDownClass(cls):
    6. print("tearDownClass")
    7. def setUp(self):
    8. print("setup")
    9. def tearDown(self):
    10. print("tearDown")
    11. def test_s(self):
    12. print("test_s")
    13. a = 1 + 1
    14. self.assertEqual(a, 2, "结果不为2")
    15. def test_b(self):
    16. print("test_b")
    17. b = 1 + 2
    18. self.assertEqual(b, 2, "结果不为2")

    if name == ‘main‘:

    1. unittest.main()

运行结果:

在这里插入图片描述

2: unittest提供的断言函数[查表]

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

3:参数化:

问题: 如果一个测试用例需要传递参数,怎么传递呢??
1: 安装包:

  1. pip install parameterized -i https://pypi.tuna.tsinghua.edu.cn/simple

2: 案例:

  1. # 1:导包
  2. import unittest
  3. from parameterized import parameterized
  4. # 2: 创建测试类
  5. class MyTest(unittest.TestCase):
  6. @parameterized.expand([('renshanwen', 23), ('niuniu', 25)])
  7. def test_param(self, name, age):
  8. print("name: %s, age: %s" % (name, age))
  9. if __name__ == '__main__':
  10. unittest.main()

3: 运行效果:
在这里插入图片描述

三: mock的使用:

1: mock的原理:

Mock是Python中一个用于支持单元测试的库,它的主要功能是使用mock对象替代掉指定的Python对象,以达到模拟对象的行为。
在这里插入图片描述

2:mock的基本使用

return_value的使用

案例: 业务需求: 由于一段代码还没有实现,但是此时需要返回一个250的数字,此时需要使用mock代替未实现的代码,返回一个250的数字,目的是让测试流程跑通。

  1. # 1:导包
  2. import unittest
  3. import unittest.mock
  4. # 2: 创建模拟的类
  5. class MyTest(unittest.TestCase):
  6. def test_return(self):
  7. # 创建一个能返回250的可调用对象
  8. mock_obj = unittest.mock.Mock(return_value=250)
  9. # 调用mocke对象,拿到返回值
  10. ret = mock_obj()
  11. print(ret)
  12. if __name__ == '__main__':
  13. unittest.main()

在这里插入图片描述

side_effect的使用:

1: 利用side_effect传递一个异常:

  1. # 1:导包
  2. import unittest
  3. import unittest.mock
  4. # 2: 创建模拟的类
  5. class MyTest(unittest.TestCase):
  6. def test_return(self):
  7. # 创建一个能返回250的可调用对象
  8. mock_obj = unittest.mock.Mock(side_effect=BaseException("自定义异常"))
  9. # 调用mocke对象,拿到返回值
  10. ret = mock_obj()
  11. print(ret)
  12. if __name__ == '__main__':
  13. unittest.main()

在这里插入图片描述
2:使用side_effect传递一个数组:

  1. import unittest.mock
  2. # 2: 创建模拟的类
  3. class MyTest(unittest.TestCase):
  4. def test_return(self):
  5. # 创建一个能返回250的可调用对象
  6. mock_obj = unittest.mock.Mock(side_effect=[1, 2, 3])
  7. # 调用mocke对象,拿到返回值
  8. for i in range(3):
  9. print(mock_obj())
  10. if __name__ == '__main__':
  11. unittest.main()

在这里插入图片描述
3: 使用side_effect传递一个函数:

  1. # 1:导包
  2. import unittest
  3. import unittest.mock
  4. # 2: 创建模拟的类
  5. class MyTest(unittest.TestCase):
  6. def test_return(self):
  7. def sum_ab(a, b):
  8. return a + b
  9. # 创建一个能返回250的可调用对象
  10. mock_obj = unittest.mock.Mock(side_effect=sum_ab)
  11. # 调用mocke对象,拿到返回值
  12. print(mock_obj(1, 2))
  13. if __name__ == '__main__':
  14. unittest.main()

在这里插入图片描述

mock的案例:

业务需求: 现在有一个需求: A负责开发支付功能,但是A还没有开发完成。B负责支付状态模块,已经完成,B代码中调用A写的代码的支付函数了。现在要求C测试B写的代码是否符合要求,C如何测试??

答: 使用mock替换A写的函数,然后调用B写的函数的时候就会调用我们的mock,不会调用A未完成的代码。
在这里插入图片描述

  1. import unittest
  2. import unittest.mock
  3. import pay
  4. import pay_status
  5. class TestPay(unittest.TestCase):
  6. def test_success(self):
  7. # 用一个mock来代替pay中的pay_way函数
  8. pay.pay_way = unittest.mock.Mock(return_value={
  9. "result": "success", "reason":"null"})
  10. # 盗用要测试的函数
  11. ret = pay_status.pay_way_status()
  12. self.assertEqual(ret, '支付成功', '支付失败')
  13. def test_fail(self):
  14. pay.pay_way = unittest.mock.Mock(return_value={
  15. "result": "fail", "reason":"余额不足"})
  16. ret = pay_status.pay_way_status() # 调用支付状态函数
  17. self.assertEqual(ret, '支付失败', '测试失败')
  18. if __name__ == '__main__':
  19. unittest.main()

在这里插入图片描述

mock的副作用:

提示: 当我们使用 mock 掉一个对象后,默认情况下,在后面执行的代码都会受到影响,比如其他的测试用例,其他的代码,只要是这些代码是在 mock 之后执行的都会受影响。

  1. import unittest
  2. import unittest.mock
  3. import pay
  4. import pay_status
  5. class TestPay(unittest.TestCase):
  6. def test_success(self):
  7. # 用一个mock来代替pay中的pay_way函数
  8. pay.pay_way = unittest.mock.Mock(return_value={
  9. "result": "success", "reason":"null"})
  10. # 盗用要测试的函数
  11. ret = pay_status.pay_way_status()
  12. self.assertEqual(ret, '支付成功', '支付失败')
  13. def test_success2(self):
  14. # 盗用要测试的函数
  15. ret = pay_status.pay_way_status()
  16. self.assertEqual(ret, '支付成功', '支付失败')
  17. if __name__ == '__main__':
  18. unittest.main()

在这里插入图片描述

对于上面的代码,test_success2,我们并没有使用mock来代替,结果也使用了mock。原因就是mock一旦被使用,后面执行的测试用例只能使用mock,不能使用原来的函数了。

限制mock只作用于当前函数的两种方式

1: patch装饰器:

1: 方法上面加上装饰器: @mock.patch(‘被代替的函数路径’)
2: 函数另外增加一个传入值。
3: 函数内部直接使用 传入值的名字.return_value = {返回值键值对}

  1. import unittest
  2. from unittest import mock
  3. import pay
  4. import pay_status
  5. class TestPay(unittest.TestCase):
  6. @mock.patch('pay.pay_way')
  7. def test_success(self, mock_pay_way):
  8. # 用一个mock来代替pay中的pay_way函数
  9. mock_pay_way.return_value={
  10. "result": "success", "reason":"null"}
  11. # 盗用要测试的函数
  12. ret = pay_status.pay_way_status()
  13. self.assertEqual(ret, '支付成功', '支付失败')
  14. if __name__ == '__main__':
  15. unittest.main()

2: patch上下文管理器:

1: 函数内部使用 : with mock.patch(‘被替代的函数路径’) as 别名:
2: 别名.return_value = {返回值键值对}

  1. import unittest
  2. from unittest import mock
  3. import pay
  4. import pay_status
  5. class TestPay(unittest.TestCase):
  6. def test_success(self):
  7. with mock.patch('pay.pay_way') as mock_pay_way:
  8. # 用一个mock来代替pay中的pay_way函数
  9. mock_pay_way.return_value={
  10. "result": "success", "reason":"null"}
  11. # 盗用要测试的函数
  12. ret = pay_status.pay_way_status()
  13. self.assertEqual(ret, '支付成功', '支付失败')
  14. if __name__ == '__main__':
  15. unittest.main()

类方法的替换:

  • mock.patch.object(类, ‘方法名’)返回指定类的函数的mock对象
    刚刚上面被替换的都是一些函数,如果想要替换一个类中的某个函数如何操作呢???

    from unittest import mock
    import unittest

    class Pay(object):

    1. def pay_way(self):
    2. """假设这里是一个支付的功能,未开发完
    3. 支付成功返回:{"result": "success", "reason":"null"}
    4. 支付失败返回:{"result": "fail", "reason":"余额不足"}
    5. reason返回失败原因
    6. """
    7. raise NotImplementedError('代码还没有实现')
    8. def pay_way_status(self):
    9. """根据支付的结果success或fail,判断跳转到对应页面
    10. 假设这里的功能已经开发完成"""
    11. # todo 此时pay_way()函数并未完成!你先假定他完成了
    12. result = self.pay_way()
    13. print(result)
    14. if result["result"] == "success":
    15. return "支付成功"
    16. if result["result"] == "fail":
    17. return "支付失败"

方案一: 传统方式:

1: 先实例化这个类
2:对象.被替换函数 = mock.Mock(return_value = {返回的键值对})
3:对象.被测函数()

  1. class TestPayStatues(unittest.TestCase):
  2. '''单元测试用例'''
  3. def test_success1(self):
  4. '''测试支付成功场景'''
  5. p = Pay() # 实例化对象
  6. p.pay_way = mock.Mock(return_value = {
  7. "result": "success", "reason":"null"})
  8. statues = p.pay_way_status()
  9. print(statues)
  10. self.assertEqual(statues, "支付成功")

方案二: 装饰器

1: 在测试函数上增加装饰器:@mock.patch.object(类, ‘被代替的方法名’)
2: 在增加一个函数传值,def test_success2(self, mock_obj):
3: 使用新传值.return_value = {返回的键值对}
4: 实例化类.被测试函数()

  1. class TestPayStatues(unittest.TestCase):
  2. @mock.patch.object(Pay, 'pay_way')
  3. def test_success2(self, mock_obj):
  4. '''测试支付成功场景'''
  5. mock_obj.return_value = {
  6. "result": "success", "reason":"null"}
  7. # 根据支付结果测试页面跳转
  8. statues = Pay().pay_way_status()
  9. print(statues)
  10. self.assertEqual(statues, "支付成功")

方案三:使用whith上下文

1: with mock.patch.object(类, ‘被代替的方法名’) as 新名字
2:新名字.return_value = {}
3: 实例化类.被测试函数()

  1. class TestPayStatues(unittest.TestCase):
  2. def test_success3(self):
  3. '''测试支付成功场景'''
  4. with mock.patch.object(Pay, 'pay_way') as mock_obj:
  5. mock_obj.return_value = {
  6. "result": "success", "reason":"null"}
  7. # 根据支付结果测试页面跳转
  8. statues = Pay().pay_way_status()
  9. print(statues)
  10. self.assertEqual(statues, "支付成功")

mock的两个属性:

  • mock.called: 决定mock是否被调用过。
  • mock.call_count : 决定mock被调用的次数。

案例测试:

  1. import unittest
  2. import unittest.mock
  3. class MockTest(unittest.TestCase):
  4. def test_return_value(self):
  5. mock_obj = unittest.mock.Mock(return_value=1999)
  6. result = mock_obj()
  7. print(result) # 打印 1999
  8. print(mock_obj.called) # 是否被调用过, 返回布尔值
  9. print(mock_obj.call_count) # 获取调用测试, 返回调用测试
  10. if __name__ == '__main__':
  11. unittest.main()

在这里插入图片描述

四: 测试报告

1:HTMLTestRunner

1: 安装包:

  1. pip install -i https://pypi.tuna.tsinghua.edu.cn/simple htmltestrunner-python3

2:核心代码:

  1. if __name__ == '__main__':
  2. # 1. 把测试用例添加到suite容器中
  3. suite = unittest.defaultTestLoader.discover('./', 'test_1_html.py')
  4. # 2. 打开文件,是一个文件对象
  5. with open('./HTMLTestRunner.html', 'w', encoding='utf-8') as f:
  6. # 3. HTMLTestRunner()创建一个runner对象
  7. runner = HTMLTestRunner(
  8. stream=f, # 测试报告需要写入到的文件
  9. verbosity=2, # 控制台输出信息的详细程度, 默认为1
  10. title='这是报告标题', # 测试报告的标题
  11. description='这是一个测试报告' # 测试报告的描述
  12. )
  13. # 4. runner把容器中测试用例运行
  14. runner.run(suite)

2: BeautifulReport

1: 安装包:

  1. pip install -i https://pypi.tuna.tsinghua.edu.cn/simple beautifulreport

2: 核心代码:

  1. if __name__ == '__main__':
  2. # 1. 把测试用例添加到suite容器中
  3. suite = unittest.defaultTestLoader.discover('./', 'test_2_beautiful.py')
  4. # 2. 创建runner对象,同时把suite传参进入
  5. runner = BeautifulReport(suite)
  6. # 3. 运行,同时生成测试报告
  7. # 参数1:生成文件的注释, 参数2:生成文件的filename, 参数3:生成report的文件存储路径
  8. runner.report('报告描述必须有,在报告中显示为用例名称', '测试报告文件名', './')

发表评论

表情:
评论列表 (有 0 条评论,387人围观)

还没有评论,来说两句吧...

相关阅读

    相关 unittest单元测试框架(10)

    单元测试(unit testing),是指对软件中的最小可测试单元进行检查和验证。对于单元测试中单元的含义,一般来说,要根据实际情况去判定其具体含义,如C语言中单元指一个函数,