unittest框架拥有支持自动化测试、测试用例间共享setUp(实现测试前的初始化工作)和shutDown(实现测试后的清理工作)代码块,集合所有的测试用例并且将测试结果独立的展示在报告框架中的特性;
unittest框架通过TestCase类来构建测试用例,并要求所有自定义的测试类都必须继承该类,它是所有测试用例的基类,传入一个测试方法名,返回一个测试用例实例;
unittest的四个重要的概念:
1、test fixture测试固件
一个test fixture代表一个或多个测试执行前的准备动作和测试结束后的清理工作,例如,创建数据库连接、启动服务器进程、测试环境的清理或者关闭数据库连接等;
2、test case 测试用例
一个test case就是一个最小测试单元,也就是一个完整的测试流程,针对一组特殊的输入进行特殊的验证与响应。通过集成unittest提供的测试基类(TestCase),可以创建新的测试用例;
3、test suite测试套件
一个test suite就是一组测试用例,一组测试套件或者两者共同组成的集合。它的作用是将测试用例集合到一起,然后一次性的执行集合中的所有测试用例;
4、test runner测试运行器
一个test runner由执行设定的测试用例和将测试结果提供给用户两部分功能组成。
单元测试的加载方式有两种:
1、直接通过unittest.main()方法加载单元测试的测试模块,这是一种最简单的方法,所有的测试方法执行顺序都是按照方法名的字符串标识的ASCII码升序排列;
2、将所有的单元测试用例(Test Case)添加到测试套件(Test Suite)集合中,然后依次性的加载所有的测试对象;
TestCase类中定义的几个特殊方法如下:
1、setUp():每个测试方法运行前运行,测试前的初始化工作;
2、tearDown():每个测试方法运行后运行,测试后的清理工作;
3、setUpDown():所有测测试方法运行前运行,单元测试的前期准备,必须使用@classmethod装饰器进行修饰,setUp()函数之前执行,整个测试过程只执行一次。
4、tearDown():所有的测试方法运行结束后执行,单元测试的后期清理,必须使用@classmethod装饰器进行修饰,tearDown()之后执行,整个测试过程只执行一次;
示例代码一:
# encoding = utf-8
import unittest
import random
class TestSequenceFunction(unittest.TestCase):
def setUp(self):
self.seq = range(10)
print self.seq
def runTest(self):
element = random.choice(self.seq)
self.assertTrue(element in self.seq)
class TestDictValueFormatFunctions(unittest.TestCase):
def setUp(self):
self.seq = range(10)
def testShuffle(self):
random.shuffle(self.seq)
print self.seq.sort()
self.assertEqual(self.seq, range(10))
self.assertRaises(TypeError, random.shuffle,(1,2,3))
if __name__ == '__main__':
unittest.main()
示例代码二:
# encoding = utf-8
import unittest
# 被测试类
class myclass(object):
@classmethod
def sum(cls, a, b):
return a+b
@classmethod
def sub(cls,a ,b):
return a-b
class myTest(unittest.TestCase):
@classmethod
def setUpClass(cls):
print '--setUpClass--'
@classmethod
def tearDownClass(cls):
print '--tearDownClass'
def setUp(self):
self.a = 1
self.b = 2
print '--setUp--'
def tearDown(self):
print '--tearDown--'
def test_sum(self):
self.assertEqual(myclass.sum(self.a, self.b), 3, 'test result right')
def test_sub(self):
self.assertEqual(myclass.sub(self.a, self.b),-1,'test result right')
if __name__== '__main__':
unittest.mian
在自动化测试的执行过程中,通常会有批量运行过个测试用例的需求,此需求称为运行测试集合(Test Suite)。将功能相关的用例组合到一起称为一个测试用例集,unittest框架中通过TestSuite类来组装所有的测试用例集;
加载测试集合步骤如下:
1、TestLoader测试用例加载器根据传入的参数获取相应的测试用例的测试方法;
2、使用makeSuite把所有的测试用例组装成test suite;
3、最后将TestSuite集合传给test Runner执行;
示例代码三:
# coding = utf-8
import random
import unittest
class TestSequenceFunctions(unittest.TestCase):
def setUp(self):
self.seq = range(10)
def tearDown(self):
pass
def test_choice(self):
element = random.choice(self.seq)
self.assertTrue(element in self.seq)
def test_sample(self):
with self.assertRaises(ValueError):
random.sample(self.seq, 20)
for element in random.sample(self.seq, 5):
self.assertTrue(element in self.seq)
class TestDictValueFormtFunction(unittest.TestCase):
def setUp(self):
self.seq = range(10)
def tearDown(self):
pass
def test_shuffle(self):
random.shuffle(self.seq)
self.seq.sort()
self.assertEqual(self.seq, range(10))
self.assertRaises(TypeError, random.shuffle, (1,2,3))
if __name__ == '__main__':
testCase1=unittest.TestLoader().loadTestsFromTestCase(TestDictValueFormtFunction)
testCase2=unittest.TestLoader().loadTestsFromTestCase(TestSequenceFunctions)
suite = unittest.TestSuite([testCase1,testCase2])
unittest.TextTestRunner(verbosity=2).run(suite)
代码解释:
1、TestLoader类:测试用例加载器,返回一个测试用例集合;
2、loadTestsFromTestCase类:根据给定的测试类,获取其中的所有以“test”开头的测试方法,并返回一个测试集合;
3、TestSuite类:组装测试用例的实例,支持添加和删除用例,最后将传递给testRunner进行测试执行;
4、TextTestRunner类:测试用例执行类,其中Text是指以文本的形式输出测试结果。
设置说明:
1、设置verbosity<=0的数字,输出结果中不提示执行成功的用例数;
2、设置verbosity=1的数字,输出结果中仅以(.)表示执行成功的用例数;
3、设置verbosity>=2的数字,输出结果中显示每个用例的详细信息,在大批量测试中可用于判断哪些测试用例执行失败;
按照特定顺序执行测试用例:
通过TestSuite类可以改变测试用例执行的顺序;
示例代码四:
Calc.py
# encoding = utf-8
class Calc(object):
def add(self, x, y, *d):
result =x+y
for i in d:
result +=i
return result
def sub(self, x, y, *d):
result=x-y
for i in d:
result -=i
return result
@classmethod
def mul(cls, x, y, *d):
result=x*y
for i in d:
result =result*i
return result
@staticmethod
def div(x, y, *d):
if y!=0:
result=x/y
else:
return -1
for i in d:
if i!=0:
result=result/i
else:
return -1
return result
MyTest.py
# encoding=utf-8
import unittest
from Calc import Calc
class MyTest(unittest.TestCase):
@classmethod
def setUpClass(cls):
print u"测试前先创建一个类实例"
cls.c=Calc()
def test_add(self):
print "run_add"
self.assertEqual(MyTest.c.add(1,2,12),15,"right")
def test_sub(self):
print "run sub"
self.assertEqual(MyTest.c.sub(12,2,3),7,"right")
def test_mul(self):
print "run_mul"
self.assertEqual(Calc.mul(2,3,5),30,'right')
def test_div(self):
print "run_div"
self.assertEqual(Calc.div(8,2,4),1,"right")
if __name__=='__main__':
unittest.main()
执行结果:
....
----------------------------------------------------------------------
Ran 4 tests in 0.000s
OK
测试前先创建一个类实例
run_add
run_div
run_mul
run sub
以unittest.main()函数启动的但单元测试,各个测试方法的执行顺序是所有方法的字符串按照ASCII码排序后的顺序执行;但我们可以通过TestSuite类可以改变测试用例执行的顺序
if __name__=='__main__':
# unittest.main()
suite = unittest.TestSuite()
suite.addTest(MyTest("test_mul"))
suite.addTest(MyTest("test_div"))
suite.addTest(MyTest("test_add"))
suite.addTest(MyTest("test_sub"))
runner = unittest.TextTestRunner()
runner.run(suite)
在批量执行测试用例时,可能会遇到某些测试用例不需要执行,但又想保留测试代码,除了可以注释掉代码以外,unittest框架提供了一个更简单的注解方法用来忽略那些暂时不需要执行的测试用例,单元测试框架在执行过程中遇到被标上忽略的注解的用例时,就会自动跳过;忽略测试用例分为有条件忽略和无条件忽略。
示例代码五:
# coding=utf-8
import random
import unittest
import sys
class TestSequenceFunctions(unittest.TestCase):
a = 1
def setUp(self):
self.seq=list(range(10))
# print self.seq
# 无条件忽略该测试方法
@unittest.skip("skipping")
def test_shuffle(self):
random.shuffle(self.seq)
self.seq.sort()
self.assertEqual(self.seq, list(range(10)))
self.assertEqual(TypeError,random.shuffle,(1,2,3))
# 如果变量a>5,则忽略掉该测试方法
@unittest.skipIf(a>5, "condition is not satisfied!")
def test_choice(self):
element=random.choice(self.seq)
self.assertTrue(element in self.seq)
# 除非执行测试用例的平台是Windows平台,否则忽略掉该测试方法
@unittest.skipUnless(sys.platform.startswith("Linux"), "requires Windows")
def test_sample(self):
with self.assertRaises(ValueError):
random.sample(self.seq, 20)
for element in random.sample(self.seq, 5):
self.assertTrue(element in self.seq)
if __name__ == '__main__':
testcases = unittest.TestLoader().loadTestsFromTestCase(TestSequenceFunctions)
suite = unittest.TestSuite(testcases)
unittest.TextTestRunner(verbosity=2).run(suite)
unittest框架支持命令行模式运行测试模块、类,甚至单独有效的测试方法。通过命令行模式,可以传入任何模块名组合、有效的测试类或者测试方法的参数列表;
1、通过命令行直接运行整个测试模块
python -m unittest test_module1 test_module2
切换进入文件所在的当前目录,然后输入如下命令:
python -m unittest -v MyTest
2、执行测试模块中某个测试类
python -m unittest -v test_module.TestClass
切换进入文件所在的当前目录,然后输入如下命令:
python -m unittest -v MyTest.MyTest
3、执行测试模块中某个测试类下的某个测试方法
python -m unittest test_module.TestClass.test_method
切换进入文件所在的当前目录,然后输入如下命令:
python -m unittest -v MyTest.MyTest.test_add MyTest.MyTest.test_sub
备注:
使用命令执行测试用例前,必须将CMD当前的工作目录切换到测试脚本文件存放目录,如果直接指定脚本文件所在路径去运行,会抛出ImportError。
这种命令执行方式对脚本所在目录名以及测试脚本文件名没有特殊要求;
命令中-v参数表示输出测试用例执行的详细信息,等价于verbosity=2;
unittest框架提供了批量执行测试模块方法,官方称测试发现。即unittest框架可以自动发现并执行给定目录下满足规则的测试模块
1、程序文件模式
该模式即将测试发现代码编写在测试脚本中,然后直接执行脚本文件即可,具体由TestLoader.discover()方法实现;
示例代码六:
新建一个工程project_discover的python工程,然后创建如下文件夹:
Clac.py
# encoding: utf-8
class Clac(object):
def add(self, a, b, *d):
result = a+b
for i in d:
result +=i
return result
def mult(self, a, b):
return a*b
testClac.py
# encoding: utf-8
import unittest
import Calc
from Calc import Clac
class MyTest(unittest.TestCase):
c = None
@classmethod
def setUpClass(cls):
print u"单元测试前,创建Calc类的实例"
cls.c = Clac()
def test_add(self):
print "run add"
self.assertEqual(MyTest.c.add(1, 2, 4), 7, 'Test add fail!')
testFact.py
# encoding: utf-8
import unittest
from Calc import Clac
class MyTestCase(unittest.TestCase):
def setUp(self):
self.num = 5
def test_Factorial(self):
seq = range(1, self.num+1)
res = reduce(lambda x, y: x*y, seq)
self.assertEqual(res, 120, "断言阶乘结果错误")
testSeqSum.py
# encoding: utf-8
import unittest
class MyTestCase(unittest.TestCase):
def test_Equal(self):
seq = range(11)
self.assertEqual(sum(seq), 55, "断言列表结果求和错误")
2、命令行模式
命令行批量执行某个目录下的测试脚本,通过unittest单元测试框架提供的discover命令实现;
备注:
上述执行命令的顺序不能修改;
在执行批量执行测试脚本命令前,必须将cmd当前的工作目录切换到存放测试脚本的目录;
discover命令还有一些其他的参数,具体说明如下:
-v:输出详细测试信息,比如python -m unittest discover -v
-s:执行发现测试脚本的目录,默认为当前目录(.),比如python -m unittest discover -v -s D:\pythonProject\project_discover
-p:模式匹配测试文件,比如python -m unittest discover -p "test*.py"
-t directory:工程的根目录下搜索可执行的测试脚本,默认是当前目录,比如:python -m unittest discover -v -t D:\pythonProject\project_discover
在单元测试中必须使用断言,unittest单元测试框架中的TestCase类提供了很多断言方法,便于检验测试是否满足预期的结果;
示例代码七:
# coding:utf-8
import unittest
import random
class MyClass(object):
@classmethod
def sum(cls, a, b):
return a+b
@classmethod
def div(cls, a, b):
return a/b
@classmethod
def return_None(cls):
return None
class MyTest(unittest.TestCase):
def test_assertEqual(self):
try:
a, b = 1, 2
sum = 13
self.assertEqual(a+b, sum, '断言失败, %s+%s!=%s' % (a,b,sum))
except AssertionError, e:
print e
def test_assertNotEqual(self):
try:
a, b = 5, 2
res = 3
self.assertNotEqual(a-b, res, '断言失败,%s - %s = %s' % (a, b, res))
except AssertionError, e:
print e
def test_assertTrue(self):
try:
self.assertTrue(1 == 1, "表达式为假")
except AssertionError, e:
print e
def test_assertFalse(self):
try:
self.assertFalse(1 !=2, '表达式为真')
except AssertionError, e:
print e
def test_assertIs(self):
try:
a=12
b=a
self.assertIs(a, b, '%s与%s不属于同一个类型' %(a, b))
except AssertionError, e:
print e
def test_assertIsNot(self):
try:
a=12
b='test'
self.assertIsNot(a, b, '%s与%s属于同一个类型' %(a, b))
except AssertionError, e:
print e
def test_assertIsNone(self):
try:
result = MyClass.return_None()
self.assertIsNone(result, "not is None")
except AssertionError, e:
print e
def test_assertIsNotNone(self):
try:
result = MyClass.sum(2, 5)
self.assertIsNotNone(result, 'is None')
except AssertionError, e:
print e
def test_assertIn(self):
# 断言对象A包含在对象B中
try:
self.seq = range(10)
self.assertIn(2, self.seq, "element is not in")
except AssertionError, e:
print e
def test_assertNotIn(self):
# 断言对象A不包含在对象B中
try:
strA ="this is a test"
strB ="Selenium"
self.assertNotIn(strB, strA, "%s包含在%s中"%(strB, strA))
except AssertionError, e:
print e
def test_assertIsInstance(self):
# 测试对象A的类型是否是指定的类型
try:
x = MyClass
y = object
self.assertIsInstance(x, y, "%s的类型不是%s"%(x, y))
except AssertionError, e:
print e
def test_assertNotIsInstance(self):
# 测试对象A的类型不是指定的类型
try:
a=123
b=str
self.assertNotIsInstance(a, b, "%s的类型是%s" %(a,b))
except AssertionError, e:
print e
def test_assertRaises(self):
# 测试抛出指定的异常类型
with self.assertRaises(ValueError) as cm:
random.sample([1, 2, 3, 4, 5], 'j')
print "===", cm.exception
try:
self.assertRaises(ZeroDivisionError, MyClass.div, 3, 0)
except ZeroDivisionError, e:
print e
if __name__ == '__main__':
unittest.main()
单元测试结束后,可以通过HTMLTestRunner生成HTML测试报告。
示例代码八:
# coding:utf-8
import unittest
import HTMLTestRunner
import math
import sys
reload(sys)
sys.setdefaultencoding("utf-8")
class Calc(object):
def add(self, x, y, *d):
result = x+y
for i in d:
result = result+i
return result
def sub(self, x, y, *d):
result = x-y
for i in d:
result -=i
return result
class SuiteTestCalc(unittest.TestCase):
def setUp(self):
self.c = Calc()
@unittest.skip("skipping")
def test_Sub(self):
print "sub"
self.assertEqual(self.c.sub(100,34,6),60,"求差结果错误")
def test_Add(self):
print "add"
self.assertEqual(self.c.add(1,32,56),89,"求和结果错误")
class SuiteTestPow(unittest.TestCase):
def setUp(self):
self.seq=range(10)
def test_Pow(self):
print "pow"
self.assertEqual(pow(6,3),216,'求幂结果错误')
def test_hasattr(self):
print "hasattr"
self.assertTrue(hasattr(math,'pow'), "检测的属性不存在")
if __name__=='__main__':
testcase1 = unittest.TestLoader().loadTestsFromTestCase(SuiteTestCalc)
testcase2 = unittest.TestLoader().loadTestsFromTestCase(SuiteTestPow)
suite = unittest.TestSuite([testcase1,testcase2])
# 定义报告存放的路径,支持相对路径
filename ='d:\\test.html'
# 以二进制形式打开文件,准备写
fp = open(filename,'wb')
# 使用HTMLTestRunner配置参数,输出报告路径,报告标题,描述
runner = HTMLTestRunner.HTMLTestRunner(stream=fp, title='Report_title', description='Report_deception')
runner.run(suite)
Pycharm不支持生成测试报告,详见链接https://blog.csdn.net/qq_39419111/article/details/81387075。
在unittest中运行第一个测试WebDriver测试用例:
# coding:utf-8
import unittest
from selenium import webdriver
import time
class GloryRoad(unittest.TestCase):
def setUp(self):
self.driver=webdriver.Chrome()
def testSogou(self):
self.driver.get("http://sogou.com")
self.driver.find_element_by_id("query").clear()
self.driver.find_element_by_id("query").send_keys(u"Webdirver实战宝典")
self.driver.find_element_by_id("stb").click()
time.sleep(3)
assert u"吴晓华" in self.driver.page_source
def tearDown(self):
self.driver.quit()
if __name__=='__main__':
unittest.main()
Python classmethod修饰符作用:
classmethod修饰符对应的函数不需要实例化,不需要self参数,但第一个参数需要是表示自身类的cls参数,可以用来调用类的属性、类的方法和实例化对象等;
#!/usr/bin/python
# -*- coding: UTF-8 -*-
class A(object):
bar = 1
def func1(self):
print ('foo')
@classmethod
def func2(cls):
print ('func2')
print (cls.bar)
cls().func1() # 调用 foo 方法
a = A()
a.func1()
# A.func2()
代码解释如下:
class A(object):
# 属性默认为类属性(可以给直接被类本身调用)
num = "类属性"
# 实例化方法(必须实例化类之后才能被调用)
def func1(self): # self : 表示实例化类后的地址id
print("func1")
print(self)
# 类方法(不需要实例化类就可以被类本身调用)
@classmethod
def func2(cls): # cls : 表示没用被实例化的类本身
print("func2")
print(cls)
print(cls.num)
cls().func1()
# 不传递传递默认self参数的方法(该方法也是可以直接被类调用的,但是这样做不标准)
def func3():
print("func3")
print(A.num) # 属性是可以直接用类本身调用的
# A.func1() 这样调用是会报错:因为func1()调用时需要默认传递实例化类后的地址id参数,如果不实例化类是无法调用的
A.func2()
A.func3()