python进阶-2.1单元测试unittest

unittest概念及原理

PyUnit(unittest) 是 Python 自带的单元测试框架,用于编写和运行可重复的测试。PyUnit 是 xUnit 体系的一个成员,xUnit 是众多测试框架的总称,PyUnit 主要用于进行白盒测试和回归测试。
unittest核心概念:

  • TestCase(测试用例): 所有测试用例的基类,它是软件 测试中最基本的组成单元。一个test case就是一个测试用例,是一个完整的测试流程,包括测试前环境的搭建setUp,执行测试代码(run),以及测试后环境的还原(tearDown)。测试用例是一个完整的测试单元,可以对某一问题进行验证。
  • TestSuite(测试套件):多个测试用例test case集合就是TestSuite,TestSuite可以嵌套TestSuite
  • TestLoder:是用来加载 TestCase到TestSuite中,其中有几个loadTestsFrom_()方法,就是从各个地方寻找TestCase,创建他们的实例,然后add到TestSuite中,再返回一个TestSuite实例
  • TextTestRunner:是来执行测试用例的,其中的run(test)会执行TestSuite/TestCase中的run(result)方法。
  • TextTestResult:测试结果会保存到TextTestResult实例中,包括运行了多少用例,成功与失败多少等信息
  • TestFixture:又叫测试脚手,测试代码的运行环境,指测试准备前和执行后要做的工作,包括setUp和tearDown方法
    测试流程:
  1. 写好TestCase:一个class继承unittest.TestCase,就是一个测试的测试用例,其中有多个以test开头的方法,那么每一个这样的,在load的时候会生成一个TestCase实例。如果一个class中有四个test开头的方法,最后load到suite中时则有四个测试用例
  2. 由TestLoder加载TestCase到TestSuite
  3. 然后由TextTestRunner来运行TestSuite,运行的结果保存在TextTestResult中。
    说明:a:通过命令行或者unittest.main()执行时,main会调用TextTestRunner中的run来执行,或者可以直接通过TextTestRunner来执行用例
    b:Runner执行时,默认将结果输出到控制台,我们可以设置其输出到文件,在文件中查看结果,也可以通过HTMLTestRunner将结果输出到HTML(这个需要下载HTMLTestRunner.py,并放到当前目录下,或者你的’D:\Python37\Lib’下,就可以导入运行了)

所有测试的本质其实都是一样的,都是通过给定参数来执行函数,然后判断函数的实际输出结果和期望输出结果是否一致。PyUnit 测试与其他 xUnit 的套路一样,基于断言机制来判断函数或方法的实际输出结果和期望输出结果是否一致,测试用例提供参数来执行函数或方法,获取它们的执行结果,然后使用断言方法来判断该函数或方法的输出结果与期望输出结果是否一致,如果一致则说明测试通过;如果不一致则说明测试不通过。

unittest中断言详解

  1. 基本的布尔断言
    即:要么正确,要么错误的验证,有一个共同点:都有一个msg参数,如果指定msg参数的值,则将该信息作为失败的错误信息返回。主要有以下:
断言方法 断言描述
assertEqual(arg1, arg2, msg=None) 验证arg1=arg2,不等则fail
assertNotEqual(arg1, arg2, msg=None) 验证arg1 != arg2, 相等则fail
assertTrue(expr, msg=None) 验证expr是true,如果为false,则fail
assertFalse(expr,msg=None) 验证expr是false,如果为true,则fail
assertIs(arg1, arg2, msg=None) 验证arg1、arg2是同一个对象,不是则fail
assertIsNot(arg1, arg2, msg=None) 验证arg1、arg2不是同一个对象,是则fail
assertIsNone(expr, msg=None) 验证expr是None,不是则fail
assertIsNotNone(expr, msg=None) 验证expr不是None,是则fail
assertIn(arg1, arg2, msg=None) 验证arg1是arg2的子串,不是则fail
assertNotIn(arg1, arg2, msg=None) 验证arg1不是arg2的子串,是则fail
assertIsInstance(obj, cls, msg=None) 验证obj是cls的实例,不是则fail
assertNotIsInstance(obj, cls, msg=None) 验证obj不是cls的实例,是则fail

代码示例:

import unittest
import sys
class demoTest(unittest.TestCase):
    def test1(self):
        self.assertEqual(4 + 5,9)     
    def test2(self):
        self.assertNotEqual(5 * 2,10)         
    def test3(self):
        self.assertTrue(4 + 5 == 9,"The result is False")                   
    def test4(self):
        self.assertTrue(4 + 5 == 10,"assertion fails")                  
    def test5(self):
        self.assertIn(3,[1,2,3])                   
    def test6(self):
        self.assertNotIn(3, range(5))
unittest.main()

结果:

E:\pythonCode>python sayhello.py
.F.F.F
======================================================
FAIL: test2 (__main__.demoTest)
------------------------------------------------------
Traceback (most recent call last):
  File "sayhello.py", line 8, in test2
    self.assertNotEqual(5 * 2,10)
AssertionError: 10 == 10
======================================================
FAIL: test4 (__main__.demoTest)
------------------------------------------------------
Traceback (most recent call last):
  File "sayhello.py", line 14, in test4
    self.assertTrue(4 + 5 == 10,"assertion fails")
AssertionError: False is not true : assertion fails
======================================================
FAIL: test6 (__main__.demoTest)
------------------------------------------------------
Traceback (most recent call last):
  File "sayhello.py", line 20, in test6
    self.assertNotIn(3, range(5))
AssertionError: 3 unexpectedly found in range(0, 5)
------------------------------------------------------
Ran 6 tests in 0.003s

FAILED (failures=3)
  1. 比较断言
    简单比较断言,例如比较a,b两个变量的值
断言方法 断言描述
assertAlmostEqual (first, second, places = 7, msg = None, delta = None) 验证first约等于second。 palces: 指定精确到小数点后多少位,默认为7
assertNotAlmostEqual (first, second, places, msg, delta) 验证first不约等于second。 palces: 指定精确到小数点后多少位,默认为7
aassertGreater (first, second, msg = None) 验证first > second,否则fail
assertGreaterEqual (first, second, msg = None) 验证first ≥ second,否则fail
assertLess (first, second, msg = None) 验证first < second,否则fail
assertLessEqual (first, second, msg = None) 验证first ≤ second,否则fail
assertRegexpMatches (text, regexp, msg = None) 验证正则表达式regexp搜索匹配的文本text。 regexp:通常使用re.search()
assertNotRegexpMatches (text, regexp, msg = None) 验证正则表达式regexp搜索不匹配的文本text。 regexp:通常使用re.search()

注: 在上述的两个函数中,如果delta指定了值,则first和second之间的差值必须≤delt
代码示例:

import unittest
import math
import sys
class demoTest(unittest.TestCase):
   def test1(self):
      self.assertAlmostEqual(22.0/7,3.14)     
   def test2(self):
      self.assertNotAlmostEqual(10.0/3,3)      
   def test3(self):
      self.assertGreater(math.pi,3)       
   def test4(self):
      self.assertNotRegexpMatches("Tutorials Point (I) Private Limited", 
   "Point")
 unittest.main()

结果:

E:\pythonCode>python sayhello.py
F..sayhello.py:42: DeprecationWarning: Please use assertNotRegex instead.
  "Point")
F
======================================================================
FAIL: test1 (__main__.demoTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "sayhello.py", line 32, in test1
    self.assertAlmostEqual(22.0/7,3.14)
AssertionError: 3.142857142857143 != 3.14 within 7 places (0.0028571428571426694
 difference)
======================================================================
FAIL: test4 (__main__.demoTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "sayhello.py", line 42, in test4
    "Point")
AssertionError: Regex matched: 'Point' matches 'Point' in 'Tutorials Point (I) P
rivate Limited'
----------------------------------------------------------------------
Ran 4 tests in 0.006s
FAILED (failures=2)
  1. 复杂断言
    可以处理元组、列表、字典等更复杂的数据类型
断言方法 断言描述
assertListEqual (list1, list2, msg = None) 验证列表list1、list2相等,不等则fail,同时报错信息返回具体的不同的地方
assertTupleEqual (tuple1, tuple2, msg = None) 验证元组tuple1、tuple2相等,不等则fail,同时报错信息返回具体的不同的地方
assertSetEqual (set1, set2, msg = None) 验证集合set1、set2相等,不等则fail,同时报错信息返回具体的不同的地方
assertDictEqual (expected, actual, msg = None) 验证字典expected、actual相等,不等则fail,同时报错信息返回具体的不同的地方

代码示例:

import unittest
import sys
class demoTest(unittest.TestCase):
   def test1(self):
      self.assertListEqual([2,3,4], [1,2,3,4,5])    
  
   def test2(self):
      self.assertTupleEqual((1*2,2*2,3*2), (2,4,6))        
   def test3(self):
      self.assertDictEqual({1:11,2:22},{3:33,2:22,1:11})  
unittest.main()

结果:

E:\pythonCode>python sayhello.py
F.F
=======================================================
FAIL: test1 (__main__.demoTest)
-------------------------------------------------------
Traceback (most recent call last):
  File "sayhello.py", line 50, in test1
    self.assertListEqual([2,3,4], [1,2,3,4,5])
AssertionError: Lists differ: [2, 3, 4] != [1, 2, 3, 4,
First differing element 0:
2
1
Second list contains 2 additional elements.
First extra element 3:
4
- [2, 3, 4]
+ [1, 2, 3, 4, 5]
?  +++       +++
=======================================================
FAIL: test3 (__main__.demoTest)
-------------------------------------------------------
Traceback (most recent call last):
  File "sayhello.py", line 57, in test3
    self.assertDictEqual({1:11,2:22},{3:33,2:22,1:11})
AssertionError: {1: 11, 2: 22} != {3: 33, 2: 22, 1: 11}
- {1: 11, 2: 22}
+ {1: 11, 2: 22, 3: 33}
?              +++++++
-------------------------------------------------------
Ran 3 tests in 0.004s

FAILED (failures=2)

unittest测试实例

在PyCharm中建立项目,工程列表如下图:
python进阶-2.1单元测试unittest_第1张图片
其中的report.html和report.txt文件是运行生成的测试结果文件
各部分代码,代码中包含操作注释:

#mathfunc.py
#待测的方法
def add(a,b):
    return a+b
def minus(a,b):
    return a-b
def multi(a,b):
    return a*b
def divide(a,b):
    return a/b




#针对mathfunc.py的测试用例
#test_mathfunc.py
import unittest
from test import mathfunc
class TestMathFunc(unittest.TestCase):# 继承unittest.TestCase
    """Test mathFunc.py"""
    def test_add(self):
        """Test method add(a,b)"""
        self.assertEqual(3,mathfunc.add(1,2))
        self.assertNotEqual(3,mathfunc.add(2,2))
    def test_minus(self):
        """Test method minus(a,b)"""
        self.assertEqual(1,mathfunc.minus(3,2))
    def test_multi(self):
        """Test method multi(a,b)"""
        self.assertEqual(6,mathfunc.multi(3,2))
    def test_divide(self):
        """Test method divide(a,b)"""
        self.assertEqual(2,mathfunc.divide(6,3))
        self.assertEqual(2.5,mathfunc.divide(5,2))





#与test_mathfunc.py的区别主要是演示setUpClass()与tearDownClass()和skipTest相关操作
#test_mathfunc2.py
import unittest
from test import mathfunc
class TestMathFunc2(unittest.TestCase):
    #如果想要在所有case执行之前准备一次环境,并在所有case执行结束后再清理环境,我们可以用setUpClass()与tearDownClass(),注意:@classmethod必须加,否则报错
    @classmethod
    def setUpClass(cls):
        print ('This setUpClass() method only called once')

    @classmethod
    def tearDownClass(cls):
        print ('This tearDownClass() method only called once too')
    #@unittest.skipTest('test_add')
    def test_add(self):
        """Test method add(a,b)"""
        self.assertEqual(3, mathfunc.add(1, 2))
        self.assertNotEqual(3, mathfunc.add(2, 2))

    def test_minus(self):
        """Test method minus(a,b)"""
        self.assertEqual(1, mathfunc.minus(3, 2))

    def test_multi(self):
        """Test method multi(a,b)"""
        self.assertEqual(6, mathfunc.multi(3, 2))

    def test_divide(self):
        """Test method divide(a,b)"""
        self.assertEqual(2, mathfunc.divide(6, 3))
        self.assertEqual(2.5, mathfunc.divide(5, 2))





#整个测试流程从加载测试用例到生成测试报告
#suit_mathfunc.py
import unittest
from test.test_mathfunc import TestMathFunc
from test.test_mathfunc2 import TestMathFunc2
from HTMLTestRunner import HTMLTestRunner
'''
#直接用addTests方法添加TestCase列表,可以确定case的执行顺序
suite=unittest.TestSuite ()
tests=[TestMathFunc('test_add'),TestMathFunc('test_minus'),TestMathFunc('test_multi'),TestMathFunc('test_divide')]
suite.addTests(tests)
runner=unittest.TextTestRunner(verbosity= 2)
runner.run(suite)
'''
'''
#直接用addTest方法添加单个TestCase
suite=unittest.TestSuite ()
tests=(TestMathFunc('test_add'))
suite.addTest(tests)
runner=unittest.TextTestRunner(verbosity= 2)
runner.run(suite)
'''
'''
#直接用addTests方法+TestLoader
suite=unittest.TestSuite ()#创建测试用例
loader=unittest.TestLoader()#加载测试用例
suite.addTests(loader.loadTestsFromName('test_mathfunc.TestMathFunc'))
runner=unittest.TextTestRunner(verbosity= 2)
runner.run(suite)
'''

#用addTests方法+TestLoader+loadTestsFromName后加列表,可测试多个模并确定执行顺序
suite=unittest.TestSuite ()#创建测试用例
loader=unittest.TestLoader()#加载测试用例
suite.addTests(loader.loadTestsFromNames(['test_mathfunc.TestMathFunc','test_mathfunc2.TestMathFunc2']))
with open('report.html','wb') as f:#输出到HTML
    runner=HTMLTestRunner(stream=f,title='mathfunc test report',description='generated by HTMLTestRunner.',verbosity= 2)
    runner.run(suite)

'''
#用addTests方法+TestLoader+loadTestsFromTestCase()方法传入TestCase名称即可
suite=unittest.TestSuite ()#创建测试用例
loader=unittest.TestLoader()#加载测试用例
suite.addTests(loader.loadTestsFromTestCase(TestMathFunc))
#生成结果到文件
with open('report.txt','w+') as f:
    runner=unittest.TextTestRunner(stream=f,verbosity= 2)
    runner.run(suite)
'''

生成的txt和HTML文件内容:

test_add (test.test_mathfunc.TestMathFunc)
Test method add(a,b) ... ok
test_divide (test.test_mathfunc.TestMathFunc)
Test method divide(a,b) ... ok
test_minus (test.test_mathfunc.TestMathFunc)
Test method minus(a,b) ... ok
test_multi (test.test_mathfunc.TestMathFunc)
Test method multi(a,b) ... ok

----------------------------------------------------------------------
Ran 4 tests in 0.001s

OK









    mathfunc test report
    
    
    






mathfunc test report

Start Time: 2019-05-21 09:30:57

Duration: 0:00:00

Status: Pass 8

generated by HTMLTestRunner.

Show Summary Failed All

Test Group/Test case Count Pass Fail Error View
test_mathfunc.TestMathFunc: Test mathFunc.py 4 4 0 0 Detail
test_add: Test method add(a,b)
pass
test_divide: Test method divide(a,b)
pass
test_minus: Test method minus(a,b)
pass
test_multi: Test method multi(a,b)
pass
test_mathfunc2.TestMathFunc2 4 4 0 0 Detail
test_add: Test method add(a,b)
pass
test_divide: Test method divide(a,b)
pass
test_minus: Test method minus(a,b)
pass
test_multi: Test method multi(a,b)
pass
Total 8 8 0 0  
 

你可能感兴趣的:(python基础)