unittest 1 2 基本概念 基本用法举例 (unittest.TestCase unittest.main())

基本概念

unittest是python自带的单元测试框架,有时候被称为"PyUnit",是python版本的JUint实现。

在学习使用unittest库之前,我们需要了解一下unittest库的一些重要概念:

  • test fixture:
    代表了用例执行前的准备工作和用例执行之后的清理工作。比如在用例执行前创建临时文件和文件夹,又或者启动1个server进程等;

  • test case:
    测试用例,这个相信大家都不陌生。是测试的最小单位,一般检查一组输入的响应(输出)是否符合预期。unittest模块提供了TestCase类来帮助我们创建测试用例;

  • test suite:
    经常被翻译成"测试套件",也有人称为"测试套",是测试用例或测试套件的集合,一般用来把需要一起执行的用例组合到一起;

  • test runner:
    用来执行测试用例并输出测试结果的组件。可以是图形界面或命令行界面;

总之test fixture的功能可以理解成是初始化和清理测试数据及环境。test case是测试用例。test suite是用例集合。test runner的作用是运行用例并返回结果。

补充,要实现更强大的功能还需要其他组件,有测试套件装入组件,参数化组件,数据驱动组件,结果报告组件等。

基本用法举例

我们通过最简单的例子来看一下unittest的基本用法,下面的代码测试了3个python字符串方法,基本上满足了大部分情况下的测试需求。

import unittest

class TestStringMethods(unittest.TestCase):

    def test_upper(self):
        self.assertEqual('foo'.upper(), 'FOO')

    def test_isupper(self):
        self.assertTrue('FOO'.isupper())
        self.assertFalse('Foo'.isupper())

    def test_split(self):
        s = 'hello world'
        self.assertEqual(s.split(), ['hello', 'world'])
        # check that s.split fails when the separator is not a string
        with self.assertRaises(TypeError):
            s.split(2)

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

解释

可以通过继承unittest.TestCase类来定义我们自己的测试用例类,1个测试用例类下面可以有多个测试方法(test)或者叫做测试点。

记住这个套路:测试用例中方法名以test开头的方法才是测试方法,比如上面的例子里定义了3个以test开头的方法,分别是test_upper,test_isupper和test_split。非测试方法是不会被test runner执行的

断言是测试用例的核心。我们使用assertEqual()来判断预期结果,用assertTrue()和assertFalse()来做是非判断,以及用assertRaises()来判断预期的异常是否有被抛出。这些unittest提供的以assert开头的方法就是断言,一般情况下,每个测试方法里都必须有断言

最后, unittest.main()提供了最简单的执行测试用例的方式。当我们从命令行运行上面的代码时,我们可以看到如下的输出

...
----------------------------------------------------------------------
Ran 3 tests in 0.000s

OK

除了使用unittest.main,还有其他的方式可以运行测试用例,比如把最后2行替换为

suite = unittest.TestLoader().loadTestsFromTestCase(TestStringMethods)
unittest.TextTestRunner(verbosity=2).run(suite)
运行用例,结果将会如下所示

test_isupper (__main__.TestStringMethods) ... ok
test_split (__main__.TestStringMethods) ... ok
test_upper (__main__.TestStringMethods) ... ok

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

OK

其他案例

单元测试作为任何语言的开发者都应该是必要的,因为时隔数月后再回来调试自己的复杂程序时,其实也是很崩溃的事情。虽然会很快熟悉内容,如果你在修改了代码后出现问题的话,修改和调试将是一件痛苦的事情。单元测试可以帮助我们很快准确的定位到问题的位置,出现问题的模块和单元。所以这是一件很愉快的事情,因为我们知道其它修改或没有修改的地方仍然是正常工作的,而我们目前的唯一问题就是搞定眼前这个有点问题的“家伙”。所以工作会在轻松中开始,并且很快将会结束,因为你已经知道很多信息了。

单元测试自然是对程序中最小的可测试模块--函数来进行测试;因为单元测试的对象是函数,也就是说你得被测试对象一定要有输出结果,哪怕就是异常输出,也得有输出,以便单元测试模块能够捕获返回值,并且与预期值进行比较,从而得出测试通过与否。

单元测试的加载方式有2种:一种是通过unittest.main()来启动单元测试的测试模块;一种是添加到testsuite集合中再加载所有的被测试对象,而testsuit里存放的就是单元测试的用例,下面分别列出了2种方法的使用。

  • 对函数进行测试

被测函数(存放在独立模块中)

def sum( x, y):  
        return x+y      
  
def sub( x, y):  
        return x-y   

测试代码(存放在独立模块中)

import unittest  
import myclass  
  
class mytest(unittest.TestCase):  
      
    ##初始化工作  
    def setUp(self):  
        pass  
      
    #退出清理工作  
    def tearDown(self):  
        pass  
      
    #具体的测试用例,一定要以test开头  
    def testsum(self):  
        self.assertEqual(myclass.sum(1, 2), 2, 'test sum fail')  
          
          
    def testsub(self):  
        self.assertEqual(myclass.sub(2, 1), 1, 'test sub fail')     
          
          
if __name__ =='__main__':  
    unittest.main()

结果
.F  
======================================================================  
FAIL: testsum (__main__.mytest)  
----------------------------------------------------------------------  
Traceback (most recent call last):  
  File "C:\Users\xiaowu\workspace\mypython\unitTest.py", line 19, in testsum  
    self.assertEqual(myclass.sum(1, 2), 2, 'test sum fail')  
AssertionError: test sum fail  
  
----------------------------------------------------------------------  
Ran 2 tests in 0.001s  
  
FAILED (failures=1) 

测试结果:【F表示一个fail, F前的点表示一个通过,有E的话表示程序自身异常】

  • 对类进行测试

被测试的类(单独模块)

class myclass:  
    def __init__(self):  
        pass  
      
      
    def sum(self, x, y):  
        return x+y  
      
      
    def sub(self, x, y):  
        return x-y   

测试类代码(单独模块)

import unittest  
import myclass  
  
class mytest(unittest.TestCase):  
      
    ##初始化工作  
    def setUp(self):  
        self.tclass = myclass.myclass()   ##实例化了被测试模块中的类  
      
    #退出清理工作  
    def tearDown(self):  
        pass  
      
    #具体的测试用例,一定要以test开头  
    def testsum(self):  
        self.assertEqual(self.tclass.sum(1, 2), 3)  
          
          
if __name__ =='__main__':  
    unittest.main()  

结果
.  
----------------------------------------------------------------------  
Ran 1 test in 0.000s  
  
OK
也可以用命令行方式执行测试模块,这种方式执行单个测试文件时使用-v参数可以获得更多的测试结果信息。如:mytest.py -v   

查看unittest模块的成员

>>import unittest  
>>dir(unittest)  
['FunctionTestCase', 'TestCase', 'TestLoader', 'TestProgram', 'TestResult', 'Tes  
tSuite', 'TextTestRunner', '_CmpToKey', '_TextTestResult', '_WritelnDecorator',  
'__all__', '__author__', '__builtins__', '__doc__', '__email__', '__file__', '__  
metaclass__', '__name__', '__package__', '__unittest', '__version__', '_makeLoad  
er', '_strclass', 'defaultTestLoader', 'findTestCases', 'getTestCaseNames', 'mai  
n', 'makeSuite', 'os', 'sys', 'time', 'traceback', 'types'] 

查看每个成员看看具体都是干什么的
>>memblist = ['FunctionTestCase', 'TestCase', 'TestLoader', 'TestProgram', 'TestResult',\  
'TestSuite','TextTestRunner', 'defaultTestLoader','findTestCases', 'getTestCaseNames', \  
'main', 'makeSuite']  
>>for memb in memblist:  
..  cur = getattr(unittest, memb)  
..  print help(cur)  
 

'FunctionTestCase':函数测试用例,即给一个函数作为参数,返回一个testcase实例,可选参数有set-up,tear-down方法
'TestCase':所有测试用例的基本类,给一个测试方法的名字,返回一个测试用例实例
'TestLoader':测试用例加载器,其包括多个加载测试用例的方法。返回一个测试套件
loadTestsFromModule(self, module)--根据给定的模块实例来获取测试用例套件
loadTestsFromName(self, name, module=None)
--根据给定的字符串来获取测试用例套件,字符串可以是模块名,测试类名,测试类中的测试方法名,或者一个可调用的是实例对象
这个实例对象返回一个测试用例或一个测试套件
loadTestsFromNames(self, names, module=None) --和上面功能相同,只不过接受的是字符串列表
loadTestsFromTestCase(self, testCaseClass)--根据给定的测试类,获取其中的所有测试方法,并返回一个测试套件
'TestProgram':命令行进行单元测试的调用方法,作用是执行一个测试用例。其实unittest.main()方法执行的就是这个命令,
而这个类实例时默认加载当前执行的作为测试对象,
原型为 init(self, module='main', defaultTest=None, argv=None, testRunner=xx, testLoader=xx)
其中module='main'就是默认加载自身
'TestResult':测试用例的结果保存实例,通常有测试框架调用
'TestSuite':组织测试用例的实例,支持测试用例的添加和删除,最终将传递给testRunner进行测试执行
'TextTestRunner':进行测试用例执行的实例,其中Text的意思是以文本形式显示测试结果。显示测试名称,即完成的测试结果,其过同执行单元测试脚本时添加-v参数
'defaultTestLoader':其实就是TestLoader
'findTestCases', 'getTestCaseNames':这个2个就不用解释了
'main': 其实就是TestProgram
'makeSuite':通常是由单元测试框架调用的,用于生产testsuite对象的实例

至此,我们知道了。其实整个单元测试框架的逻辑出来了。分三步走:第一步testloader根据传入的参数获得相应的测试用例,即对应具体的测试方法,
然后makesuite在把所有的测试用例组装成testsuite,最后把testsiute传给testrunner进行执行。
而我们通常执行的unittest.main(),其实就是unittest.testprom方法,其执行的功能就是上面分析的三步,在第一步中其传入的参数是自身的模块__main__;
在第二步中把自身模块中的所有测试类中中的测试方法提取出来,并生成测试套件;最后再把测试套件传递给testrunner进行具体的测试。

最后给出一个完整的单元测试组织代码,把该代码放到单元测试用例文件的同一个目录后执行该脚本,即可执行所有的测试用例文件。
【测试用例文件必须为test开头,如:testxxx.py, 当然这个文件本身是一个单元测试的文件】

#!/usr/bin/env python  
#encoding: utf-8  
#该代码源自深入python  
import unittest  
import myclass  
import re  
import os  
import sys  
  
  
def testAllinCurrent():  
    path = os.path.abspath(os.path.dirname(sys.argv[0]))     
    files = os.listdir(path)                                 
    test = re.compile("test\.py{1}quot;, re.IGNORECASE)            
    files = filter(test.search, files)                       
    filenameToModuleName = lambda f: os.path.splitext(f)[0]  
    moduleNames = map(filenameToModuleName, files)           
    modules = map(__import__, moduleNames)     
                    
    load = unittest.defaultTestLoader.loadTestsFromModule    
    return unittest.TestSuite(map(load, modules))  
  
if __name__ == "__main__":                     
    unittest.main(defaultTest="regressionTest")  # 此处有疑问,没有用Runner呢?

参考
python单元测试unittest实例详解 http://blog.csdn.net/five3/article/details/7104466

posted on 2020-02-09 17:59  candyYang  阅读(797)  评论(0)    收藏  举报