python unittest

本文详细介绍Python中的单元测试方法,包括unittest模块的使用技巧、测试套件的构建过程及常见断言方法的应用。通过实例演示如何对函数和类进行有效的单元测试。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

from http://blog.youkuaiyun.com/five3/article/details/7104466

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

 

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

 

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

1.1 测试模块中的函数:

被测模块:

[python]  view plain copy
  1. #!/usr/bin/env python  
  2. #encoding: utf-8  
  3.       
  4. def sum( x, y):  
  5.         return x+y  
  6.       
  7.       
  8. def sub( x, y):  
  9.         return x-y   

单元测试模块:

[python]  view plain copy
  1. #!/usr/bin/env python  
  2. #encoding: utf-8  
  3.   
  4. import unittest  
  5. import myclass  
  6.   
  7. class mytest(unittest.TestCase):  
  8.       
  9.     ##初始化工作  
  10.     def setUp(self):  
  11.         pass  
  12.       
  13.     #退出清理工作  
  14.     def tearDown(self):  
  15.         pass  
  16.       
  17.     #具体的测试用例,一定要以test开头  
  18.     def testsum(self):  
  19.         self.assertEqual(myclass.sum(12), 2'test sum fail')  
  20.           
  21.           
  22.     def testsub(self):  
  23.         self.assertEqual(myclass.sub(21), 1'test sub fail')     
  24.           
  25.           
  26. if __name__ =='__main__':  
  27.     unittest.main()  

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

[python]  view plain copy
  1. .F  
  2. ======================================================================  
  3. FAIL: testsum (__main__.mytest)  
  4. ----------------------------------------------------------------------  
  5. Traceback (most recent call last):  
  6.   File "C:\Users\xiaowu\workspace\mypython\unitTest.py", line 19in testsum  
  7.     self.assertEqual(myclass.sum(12), 2'test sum fail')  
  8. AssertionError: test sum fail  
  9.   
  10. ----------------------------------------------------------------------  
  11. Ran 2 tests in 0.001s  
  12.   
  13. FAILED (failures=1)  

1.2 测试模块类中的函数:

被测模块:

[python]  view plain copy
  1. #!/usr/bin/env python  
  2. #encoding: utf-8  
  3.   
  4. class myclass:  
  5.     def __init__(self):  
  6.         pass  
  7.       
  8.       
  9.     def sum(self, x, y):  
  10.         return x+y  
  11.       
  12.       
  13.     def sub(self, x, y):  
  14.         return x-y   

单元测试模块:

[python]  view plain copy
  1. #!/usr/bin/env python  
  2. #encoding: utf-8  
  3.   
  4. import unittest  
  5. import myclass  
  6.   
  7. class mytest(unittest.TestCase):  
  8.       
  9.     ##初始化工作  
  10.     def setUp(self):  
  11.         self.tclass = myclass.myclass()   ##实例化了被测试模块中的类  
  12.       
  13.     #退出清理工作  
  14.     def tearDown(self):  
  15.         pass  
  16.       
  17.     #具体的测试用例,一定要以test开头  
  18.     def testsum(self):  
  19.         self.assertEqual(self.tclass.sum(12), 3)  
  20.           
  21.           
  22. if __name__ =='__main__':  
  23.     unittest.main()  

运行结果:

[python]  view plain copy
  1. .  
  2. ----------------------------------------------------------------------  
  3. Ran 1 test in 0.000s  
  4.   
  5. OK  

这种方式执行单个测试文件时使用-v参数可以获得更多的测试结果信息。如:mytest.py -v 

 

2 加载测试套件

好吧,在运用测试套件进行单元测试之前,我想还是稍微研究一下unittest模块的内容有哪些,其大概的运行方式是什么样的。而后在给出根据各种情况如何制定单元测试套件。

首先,自然是查看unittest模块有哪些成员啦!

[python]  view plain copy
  1. >>import unittest  
  2. >>dir(unittest)  
  3. ['FunctionTestCase''TestCase''TestLoader''TestProgram''TestResult', 'Tes  
  4. tSuite', 'TextTestRunner', '_CmpToKey', '_TextTestResult', '_WritelnDecorator',  
  5. '__all__''__author__''__builtins__''__doc__''__email__''__file__', '__  
  6. metaclass__', '__name__', '__package__', '__unittest', '__version__', '_makeLoad  
  7. er', '_strclass', 'defaultTestLoader', 'findTestCases', 'getTestCaseNames', 'mai  
  8. n', 'makeSuite', 'os', 'sys', 'time', 'traceback', 'types']  

可以看到其自身的成员也不是很多,大概包括有:
['FunctionTestCase', 'TestCase', 'TestLoader', 'TestProgram', 'TestResult',
 'TestSuite','TextTestRunner', '_CmpToKey', '_TextTestResult', '_WritelnDecorator',
 'defaultTestLoader','findTestCases', 'getTestCaseNames', 'main', 'makeSuite']
 
 好吧,我们大概看看具体都是干什么的

[python]  view plain copy
  1. >>memblist = ['FunctionTestCase''TestCase''TestLoader''TestProgram''TestResult',\  
  2. 'TestSuite','TextTestRunner''defaultTestLoader','findTestCases''getTestCaseNames', \  
  3. 'main''makeSuite']  
  4. >>for memb in memblist:  
  5. ..  cur = getattr(unittest, memb)  
  6. ..  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, 当然这个文件本身是一个单元测试的文件】

[python]  view plain copy
  1. #!/usr/bin/env python  
  2. #encoding: utf-8  
  3. #该代码源自深入python  
  4. import unittest  
  5. import myclass  
  6. import re  
  7. import os  
  8. import sys  
  9.   
  10.   
  11. def testAllinCurrent():  
  12.     path = os.path.abspath(os.path.dirname(sys.argv[0]))     
  13.     files = os.listdir(path)                                 
  14.     test = re.compile("test\.py{1}quot;, re.IGNORECASE)            
  15.     files = filter(test.search, files)                       
  16.     filenameToModuleName = lambda f: os.path.splitext(f)[0]  
  17.     moduleNames = map(filenameToModuleName, files)           
  18.     modules = map(__import__, moduleNames)     
  19.                     
  20.     load = unittest.defaultTestLoader.loadTestsFromModule    
  21.     return unittest.TestSuite(map(load, modules))  
  22.   
  23. if __name__ == "__main__":                     
  24.     unittest.main(defaultTest="regressionTest")  
----

可以使用unitest.skip装饰器族跳过test method或者test class,这些装饰器包括:
① @unittest.skip(reason):无条件跳过测试,reason描述为什么跳过测试
② @unittest.skipif(conditition,reason):condititon为true时跳过测试
③ @unittest.skipunless(condition,reason):condition不是true时跳过测试

可以自定义skip decorator

?
1
2
3
4
5
#这是一个自定义的skip decorrator
def skipUnlessHasattr(obj, attr):
   if hasattr (obj, attr):
     return lambda func: func
   return unittest.skip( "{!r} doesn't have {!r}" . format (obj, attr))

skip decorator示例代码:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class MyTestCase(unittest.TestCase):
 
   @unittest .skip( "demonstrating skipping" )
   def test_nothing( self ):
     self .fail( "shouldn't happen" )
 
   @unittest .skipIf(mylib.__version__ < ( 1 , 3 ),
            "not supported in this library version" )
   def test_format( self ):
     # Tests that work for only a certain version of the library.
     pass
 
   @unittest .skipUnless(sys.platform.startswith( "win" ), "requires Windows" )
   def test_windows_support( self ):
     # windows specific testing code
     pass
 
@unittest .skip( "showing class skipping" )
class MySkippedTestCase(unittest.TestCase):
   def test_not_run( self ):
     pass

----

python内部自带了一个单元测试的模块,pyUnit也就是我们说的:unittest

先介绍下unittest的基本使用方法:

1.import unittest
2.定义一个继承自unittest.TestCase的测试用例类
3.定义setUp和tearDown,在每个测试用例前后做一些辅助工作。
4.定义测试用例,名字以test开头。
5.一个测试用例应该只测试一个方面,测试目的和测试内容应很明确。主要是调用assertEqual、assertRaises等断言方法判断程序执行结果和预期值是否相符。
6.调用unittest.main()启动测试
7.如果测试未通过,会输出相应的错误提示。如果测试全部通过则不显示任何东西,这时可以添加-v参数显示详细信息。

下面是unittest模块的常用方法:
assertEqual(a, b)     a == b      
assertNotEqual(a, b)     a != b      
assertTrue(x)     bool(x) is True      
assertFalse(x)     bool(x) is False      
assertIs(a, b)     a is b     2.7
assertIsNot(a, b)     a is not b     2.7
assertIsNone(x)     x is None     2.7
assertIsNotNone(x)     x is not None     2.7
assertIn(a, b)     a in b     2.7
assertNotIn(a, b)     a not in b     2.7
assertIsInstance(a, b)     isinstance(a, b)     2.7
assertNotIsInstance(a, b)     not isinstance(a, b)     2.7

下面看具体的代码应用:

首先写了一个简单应用:

import random
import unittest

class TestSequenceFunctions(unittest.TestCase):

    def setUp(self):
       self.seq = range( 10)

    def test_shuffle(self):
        # make sure the shuffled sequence does not lose any elements
       random.shuffle(self.seq)
       self.seq.sort()
       self.assertEqual(self.seq, range( 10))

        # should raise an exception for an immutable sequence
       self.assertRaises(TypeError, random.shuffle, ( 1, 2, 3))

    def test_choice(self):
       element = random.choice(self.seq)
       self.assertTrue(element  in self.seq)

    def test_error(self):
          element = random.choice(self.seq)
          self.assertTrue(element  not  in self.seq)


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

下面是写了一个简单的应用,测试下面4个网址返回的状态码是否是200。

import unittest
import urllib

class TestUrlHttpcode(unittest.TestCase):
    def setUp(self):
       urlinfo = [ 'http://www.baidu.com', 'http://www.163.com', 'http://www.sohu.com', 'http://www.cnpythoner.com']
       self.checkurl = urlinfo

    def test_ok(self):
        for m  in self.checkurl:
           httpcode = urllib.urlopen(m).getcode()
           self.assertEqual(httpcode, 200)

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

如果有的网址打不开,返回404的话,测试则会报错

 如果有的网址打不开,返回404的话,测试则会报错

  ERROR: test_ok (__main__.TestUrlHttpcode)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "jay.py", line 12, in test_ok
    httpcode = urllib.urlopen(m).getcode()
  File "/usr/lib/python2.7/urllib.py", line 86, in urlopen
    return opener.open(url)
  File "/usr/lib/python2.7/urllib.py", line 207, in open
    return getattr(self, name)(url)
  File "/usr/lib/python2.7/urllib.py", line 462, in open_file
    return self.open_local_file(url)
  File "/usr/lib/python2.7/urllib.py", line 476, in open_local_file
    raise IOError(e.errno, e.strerror, e.filename)
IOError: [Errno 2] No such file or directory: 'fewfe.com'

----------------------------------------------------------------------
Ran 1 test in 1.425s

FAILED (errors=1)
 

也有其他的unittest方法,用于执行更具体的检查,如:

Method     Checks that     New in
assertAlmostEqual(a, b)     round(a-b, 7) == 0      
assertNotAlmostEqual(a, b)     round(a-b, 7) != 0      
assertGreater(a, b)     a > b     2.7
assertGreaterEqual(a, b)     a >= b     2.7
assertLess(a, b)     a < b     2.7
assertLessEqual(a, b)     a <= b     2.7
assertRegexpMatches(s, re)     regex.search(s)     2.7
assertNotRegexpMatches(s, re)     not regex.search(s)     2.7
assertItemsEqual(a, b)     sorted(a) == sorted(b) and works with unhashable objs     2.7
assertDictContainsSubset(a, b)     all the key/value pairs in a exist in b     2.7
assertMultiLineEqual(a, b)     strings     2.7
assertSequenceEqual(a, b)     sequences     2.7
assertListEqual(a, b)     lists     2.7
assertTupleEqual(a, b)     tuples     2.7
assertSetEqual(a, b)     sets or frozensets     2.7
assertDictEqual(a, b)     dicts     2.7
assertMultiLineEqual(a, b)     strings     2.7
assertSequenceEqual(a, b)     sequences     2.7
assertListEqual(a, b)     lists     2.7
assertTupleEqual(a, b)     tuples     2.7
assertSetEqual(a, b)     sets or frozensets     2.7
assertDictEqual(a, b)     dicts     2.7

你可以用unittest模块的更多方法来做自己的单元测试。


----
ref: http://pyunit.sourceforge.net/pyunit_cn.html
ref: http://python.readthedocs.org/en/latest/library/unittest.html
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值