python 提供了PyUnit用于组织测试,是java junit程序包的一个python版本。
1 断言
断言是在代码中使用的语句,在进行开发时,可以使用它们去测试代码的有效性,如果这个语言的结果不为真,将会引发一个AssertionError错误,如果这个错误未被捕获,那么程序将会停止。
>>> large=1000
>>> assert large>500
>>> assert large<500
Traceback (most recent call last):
File "<pyshell#3>", line 1, in <module>
assert large<500
AssertionError
>>> string="this is a string"
>>> assert type(string)==type("")
>>> assert type(string)!=type("")
Traceback (most recent call last):
File "<pyshell#6>", line 1, in <module>
assert type(string)!=type("")
AssertionError
如果一个称为__debug__的特殊内部变量为True,就检查断言。如果任何一个断言没有成功,就会引发一个AssertionError错误。因为断言实际上是一个if语句与raise的组合,当出现问题时,就会raise引发一个异常,所以可以指定一个自定义的消息。
>>> try:
assert type(string)==type(1),"string is broken"
except AssertionError:print("Handle the error here.")
Handle the error here.
>>> try:
assert type(string)==type(""),"string is broken"
except AssertionError:print("Handle the error here.")
激活断言的变量__debug__是一个特殊的变量,在python启动之后它便不可改变
对python指定-O 短划线后面接着大写字母O参数,对代码进行优化,意味着除了执行其他优化处理外还将删除断言测试。
在部署一个程序的时候将用到-O,因此它删除被认为是开发阶段特性的断言
如果认为自己可以已经犯了一个错误,并希望在之后的开发周期中捕获它,那么可以放入一个断言去捕获此错误,并继续完成其他的工作,直到对该代码进行测试为止。
2 测试用例和测试套件
PyUnit是其开发者为这个程序包起的名称,但是导入的模块是:unittest
每个测试用例都是TestCase类的子类,只要重写TestCase类的runTest方法,就可以定义一个基本的测试,
可以在单个测试用例类中定义若干不同的测试方法
测试套件是同时运行于一个特定项目的一系列测试用例。
assertTrue 是testcase类的属性
import unittest #导入unittest模块
class ArithTest(unittest.TestCase):#继承TestCase类
def runTest(self):#重写runTest方法
'''test addition and succeed.'''
self.assertTrue(1+1==2,'one plus one fails!')
self.assertFalse(1+1!=2,'one plus one fails again!')
self.assertEqual(1+1,2,'more trouble with one plus one!')
def suite():
suite=unittest.TestSuite()#实例化测试套件
suite.addTest(ArithTest())#加入单元测试
return suite#返回
if __name__=='__main__':
runner=unittest.TextTestRunner()
test_suite=suite();
runner.run(test_suite)
返回单个句点表明已经成功地运行了一个单元测试,相反,如果返回一个F,测试失败了
import unittest
class ArithTest(unittest.TestCase):
def runTest(self):
'''test addition and succeed.'''
self.assertTrue(1+1==2,'one plus one fails!')
self.assertFalse(1+1!=2,'one plus one fails again!')
self.assertEqual(1+1,2,'more trouble with one plus one!')
class ArithTestFail(unittest.TestCase):
def runTest(self):
'''Test addition and fail.''' #定义文档字符串出错时会显示在错误信息中
self.assertTrue(1+1==2,'one plus one fails!')
self.assertFalse(1+1!=2,'one plus one fails again!')
self.assertEqual(1+1,2,'more trouble with one plus one!')
self.assertNotEqual(1+1,2,'expected failure here') #定义失败时指定的消息
self.assertNotEqual(1+1,2,'second failure')
def suite():
suite=unittest.TestSuite()
suite.addTest(ArithTest())
suite.addTest(ArithTestFail())
return suite
if __name__=='__main__':
runner=unittest.TextTestRunner()
test_suite=suite();
runner.run(test_suite)
=================== RESTART: E:/pythonscript/ch12/test1.py ===================
.F
======================================================================
FAIL: runTest (__main__.ArithTestFail)
Test addition and fail.
----------------------------------------------------------------------
Traceback (most recent call last):
File "E:/pythonscript/ch12/test1.py", line 14, in runTest
self.assertNotEqual(1+1,2,'expected failure here')
AssertionError: 2 == 2 : expected failure here
----------------------------------------------------------------------
Ran 2 tests in 0.018s
FAILED (failures=1)
>>>
3 测试装置
在PyUnit中,测试用例所运行的环境被称为测试装置text fixture 基本的TestCase类定义了两个方法:setUp在运行测试前调用
tearDown在测试用例完成后调用。这两个方法用于处理测试装置的创建和清理中所涉及的工作。
setUp失败,tearDown将不被调用。测试用例本身失败,tearDown被调用
当创建测试时,每个测试的初始状态不应该依赖于之前的测试成功还是失败。每个测试用例应该为自身创建一个原始的测试装置。
当在一个相同配置的测试装置上重复运行一些测试时,1 可以创建testcase类的子类来定义设置方法和清除方法,然后使用这个子类定义每个测试用例;2 也可以选择在单元测试用例类中定义若干个测试用例方法,然后为每个方法实例化测试用例对象。
import unittest
class ArithTestSupper(unittest.TestCase):
def setUp(self):
print("setting up arithTest cases")
def tearDown(self):
print("cleaning up arithtest cases")
class ArithTest(ArithTestSupper):
def runTest(self):
'''test addition and succeed.'''
print('running arithtest')
self.assertTrue(1+1==2,'one plus one fails!')
self.assertFalse(1+1!=2,'one plus one fails again!')
self.assertEqual(1+1,2,'more trouble with one plus one!')
class ArithTestFail(ArithTestSupper):
def runTest(self):
'''test addition and fail.'''
print('running arithtestfail')
self.assertNotEqual(1+1,2,'expected failure here')
self.assertNotEqual(1+1,2,'second failure')
class ArithTest2(unittest.TestCase):
def setUp(self):
print('setting up arithTest2 cases')
def tearDown(self):
print('cleaning up arithTest2 cases')
def runArithTest(self):#这里没有重写 runTest函数
'''test addition and succeed,in one class.'''
print('running arithtest in arithTest2')
self.assertTrue(1+1==2,'one plus one fails')
self.assertFalse(1+1!=2,'one plus one fails again')
self.assertEqual(1+1,2,'more trouble with one plus one')
def runArithTestFail(self):
'''Test addition and fail,in one class.'''
print('running arithtestfail in arithtest2')
self.assertNotEqual(1+1,2,'expected failure here')
self.assertNotEqual(1+1,2,'second failure')
def suite():
suite=unittest.TestSuite()
#first style:
suite.addTest(ArithTest())
suite.addTest(ArithTestFail())
#second style:
suite.addTest(ArithTest2("runArithTest"))#创建测试用例-显示调用测试方法
suite.addTest(ArithTest2("runArithTestFail"))
return suite
if __name__=='__main__':
runner=unittest.TextTestRunner()
test_suite=suite()
runner.run(test_suite)
4 用极限编程整合 XP extreme programming
对编码进行计划,然后将测试用例集合编写为一种框架; 之后才编写实际的代码。在完成编码任务的任何时候,可以重新运行该测试套件以查年接近设计目标的程序。
测试用例方法的命名约定:"test"+方法名,使unittest.main过程能够对它们进行自动地识别和运行 。
testfind.py
import unittest
import find
import os,os.path
def filename(ret):
return ret[1]
class FindTest(unittest.TestCase):
def setUp(self):
os.mkdir("_test")
os.mkdir(os.path.join("_test","subdir"))
f=open(os.path.join("_test","file1.txt"),"w")
f.write("""first line
second line
third line
fourth line""")
f.close()
f=open(os.path.join("_test","file2.py"),"w")
f.write("""this is a test file.
it has many words in it.
this is the final line.""")
f.close()
def tearDown(self):
os.unlink(os.path.join("_test","file1.txt"))
os.unlink(os.path.join("_test","file2.py"))
os.rmdir(os.path.join("_test","subdir"))
os.rmdir("_test")
def test_01_SearchAll(self):
"""1:test searching for all files."""
res=find.find(r".*",start="./_test")
print(res)
self.assertTrue(map(filename,res)==['file1.txt','file2.py'],'wrong results')
def test_02_SearchFileName(self):
'''2:test searching for specific file by regexp.'''
res=find.find(r"file",start="./_test")
self.assertTrue(map(filename,res)==['file1.txt','file2.py'],'wrong results')
res=find.find(r'py$',start="_test")
self.assertTrue(map(filename,res)==['file2.py'],'Python file search incorrect')
def test_03_SearchByContent(self):
'''3:test searching by content.'''
res=find.find(start="_test",content="first")
self.assertTrue(map(filename,res)==['file1.txt'],'did not find file1.txt')
res=find.find(where="py$",start="_test",content="line")
self.assertTrue(map(filename,res)==['file2.py'],'did not find file2.py')
res=find.find(where='py$',start='_test',content='second')
self.assertTrue(len(res)==0,'found something that did not exist')
def test_04_SearchByExtension(self):
'''4:test searching by file extension.'''
res=find.find(start="_test",ext='py')
self.assertTrue(map(filename,res)==['file2.py'],'did not find file2.py')
res=find.find(start="_test",ext='txt')
self.assertTrue(map(filename,res)==['file1.txt'],'did not find file1.txt')
def test_05_SearchByLogic(self):
'''5:test searching using a logical combination callback.'''
res=find.find(start="_test",logic=lambda x:(x['size']<50))
self.assertTrue(map(filename,res)==['file1.txt'],'failed to find by size')
if __name__=='__main__':
unittest.main()
find.py
import os,os.path
import re
from stat import *
def find(where='.*',content=None,start='.',ext=None,logic=None):
context={}
context['where']=where
context['content']=content
context['return']=[]
context['ext']=ext
context['logic']=logic
for root,dirs,files in os.walk(start):
find_file(root,files,context)
return context['return']
def find_file(root,files,context):
for file in files:
#Find out things about this file.
path=os.path.join(root,file)
path=os.path.normcase(path)
stat=os.stat(path)
size=stat[ST_SIZE]
try:
ext=os.path.splitext(file)[1][1:]
except:
ext=''
#don't treat directories like files
#if S_ISDIR(stat(ST_MODE)):continue
#do filtration based on passed logic
if context['logic'] and not context['logic'](locals()):continue
#do filtration based on extension
if context['ext'] and ext!=context['ext']:continue
#do filtration base on the original parameters of find()
if not re.search(context['where'],file):continue
#do content filtration last,to avoid it as much as possible
if context['content']:
f=open(path,'r')
match=0
for l in f.readlines():
if re.search(context['content'],l):
match=1
break
f.close()
if not match:continue
# build the return value for any files that passed the filtration tests.
file_return=(path,file,ext,size)
context['return'].append(file_return)
在软件生命周期的维护阶段,对于故障报告,应该做的第一件事是使用它修改已有的用例,或从零开始定义一个新的用例,然后才应该开始修改目标代码本身。
小结:
测试最好在开发生命周期的一开始就着手进行。
最基本的一类测试是断言。断言就是条件,它们被放置在程序中,用于确认应该存在的条件确实存在。
断言是在代码中所做的陈述,允许对代码的有效性进行测试。如果测试失败,那么将产生AssertionError错误,可以使用断言创建一些测试。
如 果python 以-O选项运行,则断言将被关闭,在一个运行的系统中,不应该依赖assert来捕获错误。
在python中,PyUnit是进行全面测试的一种默认的方法,它使得对测试过程的管理变得非常简单,PyUnit是unittest模块中实现。
应该创建TestCase类的子类,运行测试通过调用run方法来实现
建立一个测试框架的两种方法,一种是创建自定义类的一个子类,另一种是使用单独的函数实现相同的功能。
这些测试不需要与模块或程序处于相同的文件中,它们应该被放到其他地方。
一个测试套件是一系列测试用例,它们是针对一个特定的项目共同运行的。
在PyUnit中,一个测试用例的运行环境被称为测试装置,基类TestCase定义了两个方法 :setUp 在测试运行前被调用,tearDown在测试用例完成后被调用。这两个方法用于处理在测试装置的创建和清理中所涉及的任何工作 。