zoukankan      html  css  js  c++  java
  • python-单元测试unittest

    目录:

    1.unittest.TestCase中常用的断言方法

      1.1 subTest子测试

      1.2 套件测试

      1.3 批量测试单个用例

    2. 加载器

      2.1加载器协议

      2.2.执行器 TestRunner

    3.已现成的测试函数用例

    4.Mock

    Mock对象的参数:

      4.1 return_value

      4.2 side_effect

      4.3 spec

      4.4 wraps

    MagicMock对象额外方法

      4.5mock_add_spec方法

    5. patch 用模拟对象替换真实对象

    6.代码覆盖率coverage


    1. unittest.TestCase类中的常用的断言方法

    方法用途
    assertEqual(a, b) 核实 a == b
    assertNotEqual(a, b) 核实 a != b
    assertTrue(x) 核实 x 为True
    assertFalse(x) 核实 x 为False
    assertIn(itemlist) 核实itemlist
    assertNotIn(itemlist) 核实item不在list

    1.1 子测试:记录错误并测试完所有的代码

    class DemoTest(unittest.TestCase):
        def test_subtest(self):
            for i in range(5):
                with self.subTest(name=i):  # 子测试参数用于输出
                    self.assertEqual(i % 2, 0)
    
    >>>
    (djProj_py3) appledeMacBook-Air-7:tests apple$ python -m unittest test_a.DemoTest.test_subtest
    
    ======================================================================
    FAIL: test_subtest (test_a.DemoTest) (name=1)
    ----------------------------------------------------------------------
    Traceback (most recent call last):
      File "/Users/apple/PycharmProjects/work/practice/tests/test_a.py", line 19, in test_subtest
        self.assertEqual(i % 2, 0)
    AssertionError: 1 != 0
    
    ======================================================================
    FAIL: test_subtest (test_a.DemoTest) (name=3)
    ----------------------------------------------------------------------
    Traceback (most recent call last):
      File "/Users/apple/PycharmProjects/work/practice/tests/test_a.py", line 19, in test_subtest
        self.assertEqual(i % 2, 0)
    AssertionError: 1 != 0
    
    ----------------------------------------------------------------------
    Ran 1 test in 0.000s
    
    FAILED (failures=2)

    1.2测试套件:将多个用例或套件的实例组合起来,完成产品功能组级别的测试。

    分别为每个参与测试方法创建实例,并加入套件。

    class UserTest(unittest.TestCase):
    
        def test_user(self):
            self.assertTrue(True)  # 判断是否为真
    
    
    class CartTest(unittest.TestCase):
    
        def test_cart(self):
            self.assertFalse(False)
    
    
    suite = unittest.TestSuite()
    suite.addTests((UserTest('test_user'),  # 创建实例并加入套件suite
                   CartTest('test_cart'),)
                   )
    unittest.TextTestRunner(verbosity=2).run(suite)  # TextTestRunner执行器
    
    # code end!!
    >>> (djProj_py3) appledeMacBook-Air-7:tests apple$ python test_a.py test_user (__main__.UserTest) ... ok test_cart (__main__.CartTest) ... ok ---------------------------------------------------------------------- Ran 2 tests in 0.000s OK

    1.3 批量测试单个用例,可通过重写runTest

    class TestDamo(unittest.TestCase):
        def add(self):
            self.assertTrue(1)
    
        def add1(self):
            self.assertFalse('')
    
        def runTest(self):
            tests = (self.add, self.add1)
            for test in tests:
                with self.subTest(t=test):
                    test()
    
    >>>
    (djProj_py3) appledeMacBook-Air-7:tests apple$ python -m unittest -v test_a.py 
    runTest (test_a.TestDamo) ... ok
    
    ----------------------------------------------------------------------
    Ran 1 test in 0.000s
    
    OK

    2.加载器

    完整的流程是:1.discover递归目录,查找所有文件名相符的模块。loadTestsFromModule 在模块内获取所有的用例类型,再通过以loadTestsFromTestCase

    为用例的全部测试方法创建实例。最终,将之组合成测试套件交给执行器。

    注: loadTestsFromTestCase调用了getTestCaseNames查找类型中包含 特定前缀(test)的测试方法,无则选择runTest;

       loadTestsFromModule按照加载协议(load_tests),先调用load_tests函数返回自定义测试套件。仅在没有协议实现时,才返回所用的用例类型;

       可以自己创建加载器对象或使用默认的defaultTestLoader实例。

    class TestDamo(unittest.TestCase):
        def test_add(self):
            self.assertTrue(1)
    
        def test_add1(self):
            self.assertFalse('')
    
        def runTest(self):
            self.assertFalse('a')
    
    
    class Test1Damo(unittest.TestCase):
        def runTest(self):
            self.assertFalse('')
    loader = unittest.defaultTestLoader
    a = loader.loadTestsFromTestCase(TestDamo)
    print(a)
    b = loader.loadTestsFromModule(sys.modules[__name__])
    print(b)
    
    >>>
    (djProj_py3) appledeMacBook-Air-7:tests apple$ python test_a.py 
    
    <unittest.suite.TestSuite tests=[<__main__.TestDamo testMethod=test_add>, <__main__.TestDamo testMethod=test_add1>]>
    <unittest.suite.TestSuite tests=[<unittest.suite.TestSuite tests=[<__main__.Test1Damo testMethod=runTest>]>, 
                        <unittest.suite.TestSuite tests=[<__main__.TestDamo testMethod=test_add>,
                                            <__main__.TestDamo testMethod=test_add1>]>
    ]>
    # 2.1改写加载器协议
    def load_tests(loader, standard_tests, pattern): suite = unittest.TestSuite() suite.addTests(map(TestDamo, ('test_add', 'test_add1'))) return suiteb = loader.loadTestsFromModule(sys.modules[__name__])
    b = loader.loadTestsFromModule(sys.modules[__name__])
    print(b)
    >>> (djProj_py3) appledeMacBook-Air-7:tests apple$ python test_a.py 
    # 改写加载器协议后,只加载了协议指定TestDamo用例
    <unittest.suite.TestSuite tests=[<__main__.TestDamo testMethod=test_add>, <__main__.TestDamo testMethod=test_add1>]>
     

    2.2.执行器TestTunner

     用于接受用例或套件,执行测试并返回结果

    3.已现成的测试函数用例

    用FunctionTestCase包装,它本身是继承的unittest.TestCase

    def test():
        assert False
    
    
    result = unittest.FunctionTestCase(test).run()
    print(result.failures)

    >>>
    (djProj_py3) appledeMacBook-Air-7:tests apple$ python test_a.py 
    [(<unittest.case.FunctionTestCase tec=<function test at 0x10cc62bf8>>, 
    'Traceback (most recent call last): File "test_a.py", line 99, in test assert False AssertionError ')]

     4.Mock

    Mock以__getattr__拦截被mock替换对象的属性访问,动态创建‘替换对象成员’。 且新建成员同是模拟类型,以实现链式属性设置和访问。

    我对mock的理解:测试对象功能尚未完成或者依赖其他环境(例如db),可用mock替换该测试对象并指定返回结果。其作用:先完成测试逻辑,

    接触开发次序依赖

    4.1 return_value

    设置测试对象的返回值

    >>> import unittest
    >>> from unittest.mock import Mock
    >>> m = Mock()
    >>> m.func.return_value = 1
    >>> import unittest
    >>> from unittest.mock import Mock
    >>> m = Mock()
    >>> def func(a, b):return a+b
    ... 
    >>> m.func.return_value = 99
    >>> func(50, 50)
    100
    >>> m.func(50, 50)
    99
    >>> m.func(50, 50, 1)
    99
    结论:通过Mock对象指定测试用例返回值后,再通过Mock调用测试对象,并不会去执行而是直接返回return_value的值

     4.2 side_effect

    构造参数side_effect指定可调用对象,迭代器,异常。用来替代return_value返回

    # side_effect指定为可调用对象
    >>> m = Mock(side_effect=lambda x: x+10)
    >>> m(1)
    11
    
    # side_effect指定迭代器
    >>> m = Mock()
    >>> m.next = Mock(side_effect=[1,2,3])
    >>> m.next
    <Mock name='mock.next' id='4508837816'>
    >>> m.next()
    1
    >>> m.next()
    2
    >>> m.next()
    3
    >>> m.next()
    StopIteration
    
    # side_effect指定为异常
    >>> m.next = Mock(side_effect=KeyError('key error'))
    >>> m.test = Mock(side_effect=KeyError('key error'))
    >>> m.test()
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
        raise effect
    KeyError: 'key error'
    
    # side_effect的可调用对象返回值为unittest.mock.DEFAULT时,实际返回值为return_value的值
    >>> m = Mock(side_effect=lambda x: 100 if x>0 else unittest.mock.DEFAULT, return_value=99)
    >>> m(1)
    100
    >>> m(0)
    99
    # side_effect设置为None时,返回值为return_value
    >>> m.side_effect = None
    >>> m(1)
    99

    4.3 spec

    可从列表 或 某个类型提取属性名字,用以约束模拟对象mock。

    # 从列表提取属性名字
    >>> m = Mock(spec=['a', 'b'])
    >>> m.a
    <Mock name='mock.a' id='4508838992'>
    >>> m.b
    <Mock name='mock.b' id='4508838712'>
    >>> m.c
    AttributeError: Mock object has no attribute 'c'
    
    # 从类提取属性名字
    >>> class A:
    ...     a = 1
    ...     def b(self):
    ...             return 2
    ... 
    >>> m = Mock(spec=A)
    >>> m.a
    <Mock name='mock.a' id='4508836584'>
    >>> m.b
    <Mock name='mock.b' id='4508838096'>
    >>> m.c
    AttributeError: Mock object has no attribute 'c'
    # spec参数并不能阻止通过赋值创建属性 
    >>> m.c = 3  # 创建成功
    >>> m.c
    3
    
    # spec_set可以阻止赋值创建属性
    >>> m = Mock(spec_set=['a'])
    >>> m.a
    <Mock name='mock.a' id='4508838264'>
    >>> m.b
    AttributeError: Mock object has no attribute 'b'
    >>> m.b = 3  # 创建失败
    AttributeError: Mock object has no attribute 'b'
    
    # create_autospec约束参数列表,使mock模拟的对象与测试对象参数一致
    >>> m = unittest.mock.create_autospec(A, spec_set=True, instance=True)
    >>> m.test()
    TypeError: missing a required argument: 'a'
    >>> m.test(a=1)
    TypeError: missing a required argument: 'b'
    >>> m.test(a=1,b=2)
    <MagicMock name='mock.test()' id='4508838208'>
    >>> m.test.return_value = 1  # 指定模拟对象的返回值
    >>> m.test(1,2)
    1
    
    # 从lambda对象中提取参数,限制模拟对象m.test
    >>> m = Mock()
    >>> m.test = unittest.mock.create_autospec(lambda a,b: 2, return_value=1)  # 从lambda中提取参数a, b
    >>> m.test(1)
    >>> m.test = unittest.mock.create_autospec(lambda a,b: 2, return_value=1)
    >>> m.test(1,2)
    TypeError: missing a required argument: 'b'
    >>> m.test(1,2)
    1

    4.4 wraps参数

    可以将模拟对象的访问传值传递给真是对象, 这样可以在模拟和真实对象间切换,而非删除代码

    >>> class A:
    ...     def add(self, a, b):return a+b
    >>> m = Mock(spec_set=A, wraps=A())
    >>> m.add(50, 50)
    100
    # 但是一旦设置return_value,则不再传递参数给真实对象
    >>> m.add.return_value=99
    >>> m.add(50, 50)
    99

    4.5 MagicMock

    额外提供mock_add_spec方法,用于调整spec设置 

    # 重置spec_set的参数(True:spec_set, False:spec)
    >>> m.mock_add_spec(['a'], True)
    # 阻止所有属性的访问 = Mock(spec_set=[])
    >>> m.mock_add_spec([], True)
    # 取消spec or spec_set设置
    >>> m.mock_add_spec(None)

    5. patch

    使用patch将真实对象替换成模拟的对象(真是对象 x + y, 模拟对象 a去替换真实对象x,变成 a + y)

    测试用例为:

    test_a.py

    from unittest.mock import patch
    import requests

    def
    logic(url): data = requests.get(url=url) return data.status_code

    5.1 patch 上下文管理器用法

    class DemoTest(unittest.TestCase):
        def test_1(self):
            # 设置固定数据
            data = SimpleNamespace(url='https://www.baidu.com', code=200)
            with patch("test_a.logic", lambda url: data.code) as m:  # 将test_a.py下的logic方法 用 lambda去替换。
                self.assertEqual(m(data.url), data.code)
    
    (djProj_py3) appledeMacBook-Air-7:tests apple$ python -m unittest -v test_a.py 
    test_1 (test_a.DemoTest) ... ok
    
    ----------------------------------------------------------------------
    Ran 1 test in 0.000s
    
    OK

    5.2 patch 装饰器用法

      5.2.1 @patch('requests.get')  替换为 get (get自己起的名字)

    class DemoTest(unittest.TestCase):
        @patch('requests.get')
        def test_1(self, get):
            data = SimpleNamespace(url='https://www.baidu.com', code=200)
            get.return_value.status_code = data.code
            self.assertEqual(logic(data.url), data.code)
    
    (djProj_py3) appledeMacBook-Air-7:tests apple$ python -m unittest -v test_a.py 
    test_1 (test_a.DemoTest) ... ok
    
    ----------------------------------------------------------------------
    Ran 1 test in 0.001s
    
    OK

      5.2.2  将test_a.logic替换成 lambda url: 200

    class DemoTest(unittest.TestCase):
        @patch('test_a.logic', lambda url: 200)  # arg: url, return_value: 200
        def test_1(self):
            data = SimpleNamespace(url='https://www.baidu.com', code=200)
            self.assertEqual(logic(data.url), data.code)
    
    (djProj_py3) appledeMacBook-Air-7:tests apple$ python -m unittest -v test_a.py 
    test_1 (test_a.DemoTest) ... ok
    
    ----------------------------------------------------------------------
    Ran 1 test in 0.000s
    
    OK

     6.coverage代码覆盖率  pip install coverage

    pip install coverage
    
    (djProj_py3) appledeMacBook-Air-7:tests apple$ coverage run --source . -m unittest test_a.DemoTest  # 仅测试当前目录下的文件
    .
    ----------------------------------------------------------------------
    Ran 1 test in 0.000s
    
    OK
    (djProj_py3) appledeMacBook-Air-7:tests apple$ coverage report   # 测试结果生成文本
    (djProj_py3) appledeMacBook-Air-7:tests apple$ coverage html  # 测试结果生成HTML文件
  • 相关阅读:
    文本编辑器js插件
    日期时间JS插件
    原生JS写验证码
    PHP语言开发微信公众平台(订阅号)之curl命令
    PHP语言开发微信公众平台(订阅号)之开启开发者模式(3)
    PHP语言开发微信公众平台(订阅号)之注册(1)
    取出关联数组的key值和values值
    关于PHP单双引号解析变量的问题
    HDU1243:反恐训练营
    HDU1244:Max Sum Plus Plus Plus
  • 原文地址:https://www.cnblogs.com/tangpg/p/9591940.html
Copyright © 2011-2022 走看看