Python UnitTest框架,尝试一下

郑大钱呀】【】【】【】,我们一起交流,一起学习。

单元测试

单元测试(unit testing),是指对软件中的最小可测试单元进行检查和验证,但是实际上最小单元的判定是根据实际情况来的,它可能是一个函数、一个方法,也有可能是一个类,具体的颗粒度,取决于你自己。

单元测试关注的代码内部的逻辑,最小可测试单元的正确性,我们如果把一个系统比作一辆摩托车,那么最小单元就是组成摩托车的各个零部件,我们如果可以保证每个零部件没有问题,那么就可以有效减少组装时的问题。

随着项目的不断迭代,项目不断庞大,单单手工测试并不能及时发现问题,而单元测试是最早介入的,在测试金字塔模型中,是最贴近原始代码层的,在单元测试阶段发现的问题,修复成本是最少的,如果在单元测试就能发现的问题在系统测试的时候才被发现,后者所花费的时间成本可能是前者的好几倍,总的来说,bug发现的时间越靠后,所消耗的成本就越高,甚至指数级的增加。

什么是UnitTest框架?

unittest框架是python中的一款单元测试框架,我们可以先看一下官方的说明:

The unittest unit testing framework was originally inspired by JUnit and has a similar flavor as major unit testing frameworks in other languages. It supports test automation, sharing of setup and shutdown code for tests, aggregation of tests into collections, and independence of the tests from the reporting framework.

unittest单元测试框架最初受到 JUnit的启发,并且与其他语言中的主要单元测试框架具有相似的风格,它支持测试自动化、共享测试的设置和关闭代码、将测试聚合到集合中以及测试与报告框架的独立性。

在UnitTest框架中,有四大核心概念:

  1. test fixture:测试夹具(test fixture)表示执行一个或多个测试所需的准备工作,以及任何相关的清理操作,主要函数如下:setUp()tearDown()setUpClass()tearDownClass()
  2. test case:测试用例(test case)是单独的测试单元,每一个测试案例,都可以单独执行,这也是整个单元测试最主要的部分。
  3. test suite:测试套件(test suite)是测试用例、测试套件或两者的集合。它用于聚合应该一起执行的测试,套件中案例的执行顺序,与案例添加到套件里面的顺序是一致的。
  4. test runner:测试执行器是一个组件,主要用来执行测试案例,并输出测试报告。

简单示例

我们先拿一个简单的示例感受一下:

# 导入unittest库
import unittest

class TestStudy(unittest.TestCase):

    def test_case1(self):
        self.assertEqual(1, 2, "不相等")

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

运行结果如下:

F 
======================================================================
FAIL: test_case1 (__main__.TestStudy)
----------------------------------------------------------------------
Traceback (most recent call last):
    self.assertEqual(1, 2, "不相等")
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
AssertionError: 1 != 2 : 不相等

----------------------------------------------------------------------
Ran 1 test in 0.001s

FAILED (failures=1)

  1. unittest是python的内置库,不需要安装,但是使用的时候需要导入
  2. 所有的测试案例都需要继承unittest.TestCase类.
  3. 每个测试方法均以test开头,如果你的测试方法不是test开头的,那么该方法是不会执行的,哪怕是TEST也不行,必须是test
  4. assertEqual()方法为unittest框架的断言方法,是用来验证测试的结果是否符合预期。

unittest的常用断言方法

unittest 有很多的断言方法,常用的有如下几种:

assertEqual(self,值1,值2,msg=None)

该方法的主要作用是判断两个值是否相等,如果不相等的话,会输出msg中信息(即异常的信息)。

示例代码如下:

# 导入unittest库
import unittest

class TestStudy(unittest.TestCase):

    def test_case1(self):
        self.assertEqual(1, 2, "不相等")

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

执行结果如下:

F 
======================================================================
FAIL: test_case1 (__main__.TestStudy)
----------------------------------------------------------------------
PS C:\Users\PycharmProjects\UnitTestStudy> python .\main.py
F 
======================================================================              
FAIL: test_case1 (__main__.TestStudy)                                               
----------------------------------------------------------------------              
  File "C:\Users\PycharmProjects\UnitTestStudy\main.py", line 7, in test_case1
    self.assertEqual(1, 2, "不相等")
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
AssertionError: 1 != 2 : 不相等

----------------------------------------------------------------------
Ran 1 test in 0.002s

FAILED (failures=1)

assertNotEqual(self,值1,值2)

该方法的主要作用是判断两个值是否是不相等,如果相等的话,会输出msg中信息。

示例代码如下:

# 导入unittest库
import unittest

class TestStudy(unittest.TestCase):

    def test_case1(self):
        self.assertNotEqual(1, 2, "不相等")

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

执行结果如下:

.
----------------------------------------------------------------------
Ran 1 test in 0.000s

OK

assertIn(self, member,container)

该方法的主要作用是判断container是否包含member字串。

示例代码如下:

# 导入unittest库
import unittest

class TestStudy(unittest.TestCase):

    def test_case1(self):
        self.assertIn('郑', '郑大钱')

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

执行结果如下:

.
----------------------------------------------------------------------
Ran 1 test in 0.000s

OK

assertNotIn(self,member,container)

该方法的主要作用是判断container是否不包含member字串。

assertTrue(self,expr, msg=None)

该方法的主要作用是判断expr是否认为expr的表达式是否为真,如果是真的话,则通过

示例代码如下:

# 导入unittest库
import unittest

class TestStudy(unittest.TestCase):

    def test_case1(self):
        self.assertTrue(1==1)
        self.assertTrue(1>1,"1>1")

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

执行结果如下:

F 
======================================================================
FAIL: test_case1 (__main__.TestStudy)
----------------------------------------------------------------------
PS C:\Users\PycharmProjects\UnitTestStudy> python .\main.py
F 
======================================================================              
FAIL: test_case1 (__main__.TestStudy)                                               
----------------------------------------------------------------------              
  File "C:\Users\PycharmProjects\UnitTestStudy\main.py", line 8, in test_case1
    self.assertTrue(1>1,"1>1")
    ^^^^^^^^^^^^^^^^^^^^^^^^^^
AssertionError: False is not true : 1>1

----------------------------------------------------------------------
Ran 1 test in 0.001s

FAILED (failures=1)

我们在一个案例中设置了多个断言,但是只要有一个断言,没有通过,这个案例就是失败的。

assertFalse(self, expr, msg=None)

该方法的主要作用是判断expr是否认为expr的表达式是否为假,如果是假的话,则通过。

assertIsNone(self, obj)

该方法的主要作用是判断obj是否为None,如果为None的话,则通过。

示例代码如下:

# 导入unittest库
import unittest

class TestStudy(unittest.TestCase):

    def test_case1(self):
        per = None
        self.assertIsNone(per)

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

执行结果如下:

.
----------------------------------------------------------------------
Ran 1 test in 0.000s

OK

assertIsNotNone(self, obj)

该方法的主要作用是判断obj是否不为None,如果不为None的话,则通过。

assertIsInstance(a, b)

该方法主要判断两个实例是否为某数据类型,如果是则通过,示例代码如下:

# 导入unittest库
import unittest
from loguru import logger

class TestStudy(unittest.TestCase):

    def test_case1(self):
        a = "郑大钱"
        logger.info("case1")
        self.assertIsInstance(a,int)


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

assertNotIsInstance(a, b)(a, b)

该方法主要判断两个实例是否不为某数据类型,如果不是,则通过

setUp和tearDown方法

如果案例都同时存在相同的前置操作,或者后置操作,此时就可以实现setUp和tearDown方法,每个测试方法在执行前和执行后,都会调用这两个方法,示例代码如下:

# 导入unittest库
import unittest
from loguru import logger

class TestStudy(unittest.TestCase):

    def setUp(self):
        logger.info("setUp方法")
    def tearDown(self):
        logger.info("tearDown方法")

    def test_case1(self):
        per = None
        self.assertIsNone(per)

    def test_case2(self):
        per = 1
        self.assertIsNone(per)

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

执行结果如下:

PS C:\Users\PycharmProjects\UnitTestStudy> python .\main.py
2022-02-03 16:31:04.810 | INFO     | __main__:setUp:8 - setUp方法                    
2022-02-03 16:31:04.812 | INFO     | __main__:tearDown:10 - tearDown方法             
.2022-02-03 16:31:04.813 | INFO     | __main__:setUp:8 - setUp方法                   
F2022-02-03 16:31:04.815 | INFO     | __main__:tearDown:10 - tearDown方法            
                                                                                     
======================================================================               
FAIL: test_case2 (__main__.TestStudy)                                                
----------------------------------------------------------------------               
  File "C:\Users\PycharmProjects\UnitTestStudy\main.py", line 18, in test_case2
    self.assertIsNone(per)
    ^^^^^^^^^^^^^^^^^^^^^^
AssertionError: 1 is not None

----------------------------------------------------------------------
Ran 2 tests in 0.007s

FAILED (failures=1)
PS C:\Users\PycharmProjects\UnitTestStudy> 

setUpClass(tearDownClass)方法

有些时候,我们只需要前置和后置条件,只运行一次,此时就需要使用setUpClass和和tearDownClass方法,值得注意的是,这两个是类方法,需要使用@classmethod来修饰,而setUp和tearDown是实例方法,不需要使用@classmethod来修饰。

示例代码如下:

# 导入unittest库
import unittest
from loguru import logger

class TestStudy(unittest.TestCase):
    @classmethod
    def setUpClass(cls):
        logger.info("setUpClass")

    @classmethod
    def tearDownClass(cls):
        logger.info("tearDownClass")

    def setUp(self):
        logger.info("setUp")
    def tearDown(self):
        logger.info("tearDown")


    def test_case1(self):
        logger.info("case1")

    def test_case2(self):
        logger.info("case2")

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

执行结果如下:

PS C:\Users\PycharmProjects\UnitTestStudy> python .\main.py
2022-02-03 17:22:45.734 | INFO     | __main__:setUpClass:8 - setUpClass
2022-02-03 17:22:45.736 | INFO     | __main__:setUp:15 - setUp
2022-02-03 17:22:45.738 | INFO     | __main__:test_case1:21 - case1
2022-02-03 17:22:45.739 | INFO     | __main__:tearDown:17 - tearDown
.2022-02-03 17:22:45.741 | INFO     | __main__:setUp:15 - setUp
2022-02-03 17:22:45.742 | INFO     | __main__:test_case2:24 - case2
2022-02-03 17:22:45.743 | INFO     | __main__:tearDown:17 - tearDown
.2022-02-03 17:22:45.745 | INFO     | __main__:tearDownClass:12 - tearDownClass

----------------------------------------------------------------------
Ran 2 tests in 0.013s

OK
PS C:\Users\PycharmProjects\UnitTestStudy>

跳过案例

假如我们定义了两个案例,但是第二个案例,我暂时不想让他运行,怎么办呢,在unittest框架中,我们可以使用装饰器@unittest.skip("MSG"),跳过测试方法,也可以跳过测试类,示例代码如下:

# 导入unittest库
import unittest
from loguru import logger

class TestStudy(unittest.TestCase):

    def setUp(self):
        logger.info("setUp方法")
    def tearDown(self):
        logger.info("tearDown方法")

    def test_case1(self):
        per = None
        self.assertIsNone(per)

    @unittest.skip("跳过案例2")
    def test_case2(self):
        per = 1
        self.assertIsNone(per)

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

执行结果如下:

PS C:\Users\PycharmProjects\UnitTestStudy> python .\main.py
2022-02-03 16:49:53.547 | INFO     | __main__:setUp:8 - setUp方法
2022-02-03 16:49:53.549 | INFO     | __main__:tearDown:10 - tearDown方法
.s
----------------------------------------------------------------------
Ran 2 tests in 0.004s

OK (skipped=1)
PS C:\Users\PycharmProjects\UnitTestStudy>

我们上面是固定的跳过某个案例,但是如果当满足一定条件才跳过,不满足则不跳,怎么办呢,此时我们可以使用@unittest.skipIf,当条件为真时,跳过被装饰的测试,示例代码如下:

# 导入unittest库
import unittest
from loguru import logger

class TestStudy(unittest.TestCase):
    skip_case_id = 2

    def test_case1(self):
        per = None
        self.assertIsNone(per)

    @unittest.skipIf(skip_case_id==2,'跳过案例2')
    def test_case2(self):
        per = 1
        self.assertIsNone(per)

    @unittest.skipIf(skip_case_id==3,'跳过案例3')
    def test_case3(self):
        per = 1
        self.assertIsNone(per)

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

执行结果如下:

PS C:\Users\PycharmProjects\UnitTestStudy> python .\main.py
.sF                                                                                  
======================================================================               
FAIL: test_case3 (__main__.TestStudy)                                                
----------------------------------------------------------------------               
Traceback (most recent call last):
  File "C:\Users\PycharmProjects\UnitTestStudy\main.py", line 20, in test_case3
    self.assertIsNone(per)
    ^^^^^^^^^^^^^^^^^^^^^^
AssertionError: 1 is not None

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

FAILED (failures=1, skipped=1)
PS C:\Users\PycharmProjects\UnitTestStudy>

@unittest.skipIf相反的有一个@unittest.skipUnless装饰器,即当条件不满足的时候,跳过案例,如果满足则执行,示例代码吗如下:

# 导入unittest库
import unittest
from loguru import logger

class TestStudy(unittest.TestCase):
    skip_case_id = 2

    def test_case1(self):
        per = None
        self.assertIsNone(per)

    @unittest.skipUnless(skip_case_id!=2,'跳过案例2')
    def test_case2(self):
        per = 1
        self.assertIsNone(per)

    @unittest.skipIf(skip_case_id==3,'跳过案例3')
    def test_case3(self):
        per = 1
        self.assertIsNone(per)

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

执行结果如下:

PS C:\Users\PycharmProjects\UnitTestStudy> python .\main.py
.sF 
======================================================================
FAIL: test_case3 (__main__.TestStudy)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "C:\Users\PycharmProjects\UnitTestStudy\main.py", line 20, in test_case3
    self.assertIsNone(per)
    ^^^^^^^^^^^^^^^^^^^^^^
AssertionError: 1 is not None

----------------------------------------------------------------------
Ran 3 tests in 0.002s

FAILED (failures=1, skipped=1)
PS C:\Users\PycharmProjects\UnitTestStudy>

期望案例失败

有的时候,我们会编写一些反例,预期结果即使失败的,此时我们就可以使用@unittest.expectedFailure装饰器,来修饰测试方法,示例如下:

# 导入unittest库
import unittest
from loguru import logger

class TestStudy(unittest.TestCase):
    skip_case_id = 2

    def test_case1(self):
        per = None
        logger.info("case1")
        self.assertIsNone(per)


    @unittest.expectedFailure
    def test_case2(self):
        logger.info("case2")
        per = 1
        self.assertIsNone(per)

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

执行结果如下:

PS C:\Users\PycharmProjects\UnitTestStudy> python .\main.py
2022-02-03 17:14:04.718 | INFO     | __main__:test_case1:10 - case1
.2022-02-03 17:14:04.721 | INFO     | __main__:test_case2:16 - case2
x
----------------------------------------------------------------------
Ran 2 tests in 0.005s

OK (expected failures=1)
PS C:\Users\PycharmProjects\UnitTestStudy>

发生异常时跳过案例执行

如果你想案例发生异常时,跳过案例,你可以使用来跳过测试,实例代码如下:

# 导入unittest库
import unittest
from loguru import logger

class TestStudy(unittest.TestCase):

    def test_case1(self):
        logger.info("case1")

    def test_case2(self):
        try:
            a=1/0
            logger.info("case2")
        except BaseException as e:
            logger.error(e)
            unittest.SkipTest("reason")

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

执行结果如下:

PS C:\Users\PycharmProjects\UnitTestStudy> python .\main.py
2022-02-03 17:34:18.654 | INFO     | __main__:test_case1:9 - case1
.2022-02-03 17:34:18.656 | ERROR    | __main__:test_case2:16 - division by zero
.
----------------------------------------------------------------------
Ran 2 tests in 0.004s

OK
PS C:\Users\PycharmProjects\UnitTestStudy>

TestSuite测试组件

TestSuite测试组件,我们可以理解成一个测试集,我们以前运行案例的时候,都是使用unittest.main()方法,我们也可以使用TestSuite的方式,示例代码如下:

# 导入unittest库
import unittest
from loguru import logger

class TestStudy(unittest.TestCase):

    def test_case1(self):
        logger.info("case1")

    def test_case2(self):
        logger.info("case1")
    def test_case3(self):
        logger.info("case1")


if __name__ == '__main__':
    suite = unittest.TestSuite()
    # 装载测试用例,也可以使用addTests()方法一次性装载多个测试案例
    suite.addTest(TestStudy("test_case1"))
    suite.addTest(TestStudy("test_case2"))
    suite.addTest(TestStudy("test_case3"))
    # 运行测试案例
    runner = unittest.TextTestRunner(verbosity=2)
    runner.run(suite)

执行结果如下:

test_case1 (__main__.TestStudy) ... 2022-02-03 18:01:25.935 | INFO     | __main__:test_case1:8 - case1  
ok
test_case2 (__main__.TestStudy) ... 2022-02-03 18:01:25.937 | INFO     | __main__:test_case2:11 - case1
ok
test_case3 (__main__.TestStudy) ... 2022-02-03 18:01:25.939 | INFO     | __main__:test_case3:13 - case1
ok

----------------------------------------------------------------------
Ran 3 tests in 0.006s

TestLoader用例加载器

我们上面添加案例的方式,是通过TestSuite,一个一个添加的,但是太麻烦,我们可以先通过用例加载器,直接从类和模块创建测试套件,示例代码如下:

# 导入unittest库
import unittest
from loguru import logger

class TestStudy(unittest.TestCase):

    def test_case1(self):
        logger.info("case1")

    def test_case2(self):
        logger.info("case1")
    def test_case3(self):
        logger.info("case1")


if __name__ == '__main__':
    # 创建一个用例集
    suite = unittest.TestSuite()
    # 创建一个加载器
    loader = unittest.TestLoader()
    # 通过测试类的类名,将案例加载进案例加载器中
    cases = loader.loadTestsFromTestCase(TestStudy)
    # 将加载器中案例,添加到用例集中
    suite.addTest(cases)

    # 运行测试案例
    runner = unittest.TextTestRunner(verbosity=2)
    runner.run(suite)

执行结果如下:

PS C:\Users\PycharmProjects\UnitTestStudy> python .\main.py
test_case1 (__main__.TestStudy) ... 2022-02-04 15:46:02.877 | INFO     | __main__:test_case1:8 - case1 
PS C:\Users\PycharmProjects\UnitTestStudy> python .\main.py
test_case1 (__main__.TestStudy) ... 2022-02-04 15:46:07.440 | INFO     | __main__:test_case1:8 - case1  
ok
test_case2 (__main__.TestStudy) ... 2022-02-04 15:46:07.442 | INFO     | __main__:test_case2:11 - case1
ok
test_case3 (__main__.TestStudy) ... 2022-02-04 15:46:07.444 | INFO     | __main__:test_case3:13 - case1
ok

----------------------------------------------------------------------
Ran 3 tests in 0.006s

OK
PS C:\Users\PycharmProjects\UnitTestStudy>

从多文件中,加载案例

上面我们都是只有一个测试类,但是当我们的案例很多的时候,就需要分文件管理,当有多个文件的时候,我们怎么去获取这些文件的案例呢,假如我们的案例结构是这样的:

├───cases
│   └───case1.py
│   └───case2.py
├───main.py

case1.py的代码如下:

import unittest
from loguru import logger

class TestCase1(unittest.TestCase):

    def test_case1(self):
        logger.info("case1-case1")

    def test_case2(self):
        logger.info("case1-case2")

case2.py的代码如下:

import unittest
from loguru import logger

class TestCase2(unittest.TestCase):

    def test_case1(self):
        logger.info("case2-case1")

    def test_case2(self):
        logger.info("case2-case2")

main.py的代码如下:

if __name__ == '__main__':
    discover = unittest.defaultTestLoader.discover('./cases',pattern='case*.py',top_level_dir=None)
    runner = unittest.TextTestRunner()
    runner.run(discover)

执行结果如下:

PS C:\Users\PycharmProjects\UnitTestStudy> python .\main.py
2022-02-04 15:58:22.388 | INFO     | case1:test_case1:8 - case1-case1
.2022-02-04 15:58:22.390 | INFO     | case1:test_case2:11 - case1-case2
.2022-02-04 15:58:22.391 | INFO     | case2:test_case1:7 - case2-case1
.2022-02-04 15:58:22.393 | INFO     | case2:test_case2:10 - case2-case2
.
----------------------------------------------------------------------
Ran 4 tests in 0.007s

OK
PS C:\Users\PycharmProjects\UnitTestStudy>

TextTestRunner执行器

TextTestRunner执行器,执行结果以文本的形式返回,unittest除了有文本的执行器,也有HTML的执行器(HTMLTestRunner),只不过需要额外的安装,这里就不在演示了,我们看一下TextTestRunner执行器返回的测试结果,主要有那些信息,代码如下:

if __name__ == '__main__':
    discover = unittest.defaultTestLoader.discover('./cases',pattern='case*.py',top_level_dir=None)
    runner = unittest.TextTestRunner()
    # 返回测试结果
    reult = runner.run(discover)

    # 测试已执行的总数,放在最后,其实就是执行的总数
    logger.info(reult.testsRun)
    # 案例是否都成功
    logger.info(reult.wasSuccessful())
    # 案例跳过的情况
    logger.info(reult.skipped)
    # 案例错误的情况
    logger.info(reult.errors)
    # 案例失败的情况
    logger.info(reult.failures)
    # 期望失败的数量
    logger.info(reult.expectedFailures)

执行结果如下:

PS C:\Users\PycharmProjects\UnitTestStudy> python .\main.py
2022-02-04 16:25:00.643 | INFO     | case1:test_case1:10 - case1-case1  
.s2022-02-04 16:25:00.645 | INFO     | case2:test_case1:7 - case2-case1 
.2022-02-04 16:25:00.647 | INFO     | case2:test_case2:10 - case2-case2 
.                                                                       
----------------------------------------------------------------------  
Ran 4 tests in 1.007s

OK (skipped=1)
2022-02-04 16:25:00.649 | INFO     | __main__:<module>:23 - 4
2022-02-04 16:25:00.652 | INFO     | __main__:<module>:25 - True
2022-02-04 16:25:00.653 | INFO     | __main__:<module>:27 - [(<case1.TestCase1 testMethod=test_case2>, '跳过case1-case2')]
2022-02-04 16:25:00.655 | INFO     | __main__:<module>:29 - []
2022-02-04 16:25:00.657 | INFO     | __main__:<module>:31 - []
2022-02-04 16:25:00.659 | INFO     | __main__:<module>:33 - []
PS C:\Users\PycharmProjects\UnitTestStudy>  

```python
在这里插入代码片
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

郑大钱呀

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值