自动化测试框架-----unittest篇_unittest项目结构-程序员宅基地

技术标签: android  软件测试  

框架的概念

在系统开发过程中,框架是指对特定应用领域中的应用系统的部分设计和实现子系统的整体结构。
框架将应用系统划分为类和对象,定义类和对象的责任,类和对象如何相互协作,以及对象之间的控制线程。这些共有的设计因素由框架预先定义,应用开发人员只须关注于特定的应用系统特有部分。
自动化测试框架的定义为:
由一个或多个自动化测试基础模块、自动化测试管理模块、自动化测试统计模块等组成的工具集合。

  • 按框架的定义来分,自动化测试框架可以分为:基础功能测试框架、管理执行框架;
  • 按不同的测试类型来分,可以分为:功能自动化测试框架、性能自动化测试框架;
  • 按测试阶段来分,可以分为:单元自动化测试框架、接口自动化测试框架、系统自动化测试框架;
  • 按组成结构来分,可以分为:单机自动化测试框架、综合自动化测试框架。
  • 按部署方式来分,可以分为:单机自动化测试框架、分布式自动化测试框架。

Unittest单元测试框架

Unittest框架(又名PyUnit框架)为Python语言的单元测试框架。
其官方介绍文档链接为:25.3. unittest — Unit testing framework — Python 2.7.18 documentation

Unittest测试框架使用介绍

1.用import语句引入unittest模块
2.让所有执行测试的类都继承于TestCase类,可以将TestCase看成是对特定类进行测试的方法的集合
3.setUp()方法中进行测试前的初始化工作,teardown()方法中执行测试后的清除工作,它们都是TestCase中的方法
4.编写测试的方法最好以test开头(可以直接运行)def test_add(self)、def test_sub(self)等,可以编写多个测试用例对被测对象进行测试
5.在编写测试方法过程中,使用TestCase class提供的方法测试功能点,比如:assertEqual等
6.调用unittest.main()方法运行所有以test开头的方法

应用实例:
对模块中的加法功能测试
存在一个calc.py的模块,里面存在一个加法功能

def sum(a,b):
return a+b
#encoding:utf-8
import unittest #导入unittest
import calc #导入被测模块
class mytest(unittest.TestCase):
def setUp(self): #初始化工作
pass
def tearDown(self): #退出清理工作
pass
def testsum(self): #具体的测试用例,一定要以test开头
self.assertEqual(calc.sum(1,2),2,"testing sum")
if __name__ == "__main__":
unittest.main()

实例:

#被测对象 a.py
def add(a,b):
return a+b
#测试类 Test.py
import unittest
from common.a import add
class Test(unittest.TestCase):
def setUp(self): #重写父类setUp的方法,用于执行用例之前的准备工作。
print("setUp")
#pass
def tearDown(self): ##重写父类的tearDown的方法。用于执行用例之后的扫尾工作。
print("tearDown")
#pass
#测试用例方法用于测试add的函数,这个方法必须以test开头
def test_int_add(self):
print("test_int_add")
sum = add(10,20)
#断言结果
self.assertEqual(sum,30)
def test_float_add(self):
print("test_float_add")
sum = add(10.2,20.8)
#断言结果
self.assertEqual(sum,31)
if __name__ == '__main__':
unittest.main()

常用的assert语句

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
assertIsNot(a,b) a is not b
assertIsNone(x) x is None
assertIsNotNone(x) x is not None
assertIn(a,b) a in b
assertNotIn(a,b) a not in b
assertNotIsInstance(a,b) not isinstance(a,b)
assertGreater(a,b) a>b
assertGreaterEqual(a,b) a>=b
assertLess(a,b) a<b
assertLessEqual(a,b) a<=b

unittest创建测试代码的方式:

#方式一:创建子类继承unittest.TestCase,然后重写runTest方法
class WidgetTestCase(unittest.TestCase):
def setUp(self):
pass
def runTest(self):
pass
def tearDown(self):
pass
#方式二:编写以test开头的方法
class WidgetTestCase(unittest.TestCase):
def setUp(self):
pass
def test_xx1(self)
def test_xx2(self)
...
def test_xxN(self)
def tearDown(self):
pass

unittest构建测试套件(测试用例集合):

前提Tester是继承了unittest.TestCase的子类

#方式一:
Suite=unittest.TestSuite()
Suite.addTest(Tester('test_default_size'))
Suite.addTest(Tester('test_resize'))
#方式二(推荐):
def suite():
Suite=unittest.TestSuite()
Suite.addTest(Tester('test_default_size'))
Suite.addTest(Tester('test_resize'))
return suite
#方式三(推荐):
def suite():
tests=['test_default_size','test_resize']
return unittest.TestSuite(map(Tester,tests))
#构建测试套件举例:
#存在如下类calc:
#encoding:utf-8
class calc(): #计算器类
def __init__(self,a,b): #初始化
self.numa=a
self.numb=b
def sum(self): #加法
return self.numa+self.numb
def sub(self): #减法
return self.numa-self.numb
def multi(self): #乘法
pass
#测试方法
import unittest #导入unittest
import calc
class mytest(unittest.TestCase):
def setUp(self): #初始化工作
self.testnum=calc.calc(3,4)
def tearDown(self): #退出清理工作
del self.testnum
def testsum(self): #具体的测试用例
self.assertEqual(self.testnum.sum(),7,"testing sum")
def testsub(self): #具体的测试用例
self.assertEqual(self.testnum.sub(),-1,"testing sub")
def testmulti(self): #具体的测试用例
self.assertEqual(self.testnum.multi(),12,"testing multi")
def suite():
Suite=unittest.TestSuite()
Suite.addTest(mytest('testsum'))
Suite.addTest(mytest('testsub'))
return suite
if __name__=="__main__":
unittest.main(defaultTest='suite')

unittest忽略测试用例:

unittest支持忽略部分测试用例不执行,分无条件忽略和有条件忽略,通过装饰器实现。
使用unittest.skip装饰器族跳过test method或者test class,这些装饰器包括:
[email protected](reason):无条件跳过测试,reason描述为什么跳过测试
[email protected](condition,reason):condition为true时跳过测试
[email protected](condition,reason):condition不是true时跳过测试
4.@expected failure:使用@unittest.expectedFailure装饰器,如果test失败了,这个test不计入失败的case数目

举例:


def testsub(self): #具体的测试用例
self.assertEqual(self.testnum.sub(),-1,"testing sub")
@unittest.skip("test skipping") #跳过测试用例
def testmulti(self): #具体的测试用例
self.assertEqual(self.testnum.multi(),12,"testing multi")

运行测试集

unittest使用TestRunner类作为测试用例的基本执行环境,来驱动整个单元测试过程,在单元测试时,一般不直接使用TestRunner类,而是使用其子类TextTestRunner来完成测试,并将结果以文本方式显示出来。

Runner = unittest.TestTestRunner()
runner.run(suite)

同时,在unittest模块中定义了一个名为main的全局方法,使用它可以很方便地将一个单元测试模块变成可以直接运行的测试脚本,main()方法使用TestLoader类来搜索所有包含在该模块中的测试方法,并自动执行它们。按照(以test开头)来命名所有测试方法,只需要在测试模块的最后加入如下几行代码:

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

或者加参数如下:

unittest.main(defaultTest='suite')

批量执行测试用例

通过前面的介绍,我们可以在一个.py文件里面编写多个测试用例,然后执行文件里的所有测试用例,但是如果测试用例数量过多,放一文件里面就不合理了。
比较合理的做法是把相关的几条用例放到一个.py文件里,把所有.py的文件放到一个文件夹下,然后通过一个程序执行文件夹里面所有用例。

目录结构:

  • unittest
    • test_case
      • search.py
      • baidu.py
    • test_case.py

baidu.py内容如下:

#-*- coding:utf-8 -*-
from selenium import webdriver
from selenium.webdriver.common.action_chains import ActionChains
import time,os,unittest
class baidu(unittest.TestCase):
def setUp(self):
Chromedi="D:\Google\Chrome\Application\chromedirver.exe"
os.enciron["webdriver.Chrome.driver"]=Chromedi
self.driver=webdriver.Chrome(Chromedi)
self.baseurl="http://www.baidu.com"
self.verificationErrors=[]
self.accpet_next_alert=True
#测试百度知道链接是否正确
def test_baidu_set(self):
driver=self.driver
driver.maxmize_window()
driver.get(self.baseurl+"/")
m=dirver.find_element_by_name("tj_briicon")
ActionChains(driver).move_to_element(m).perform()
driver.find_element_by_partial_link_text("知道").click()
# self.assertEqual(driver.current_url,"http://zhidao.baidu.com/")
self.assertEqual(driver.title,u"百度知道-全球最大中文互动问答平台")
time.sleep(2)
def tearDown(self):
self.driver.quit()
if __name__=="__main__":
unittest.main()

search.py内容如下:

#-*- coding:utf-8 -*-
from selenium import webdriver
import time,os,unittest
class search(unittest.TestCase):
def setUp(self):
Chromedi="C:\Google\Chrome\Application\chromedriver.exe"
os.environ['webdriver.Chrome.driver']=Chromedi
self.driver=webdriver.Chrome(Chromedi)
self.baseurl="http://www.baidu.com"
self.verificationErrors=[]
self.accpet_next_alert=True
#测试百度搜索
def test_search(self):
driver=self.driver
driver.maximize_window()
driver.get(self.baseurl+"/")
driver.find_element_by_css_selector("#kw").send_keys("12306")
driver.find_element_by_css_selector("#su1").click()
def tearDown(self):
self.driver.quit()
self.assertEqual([],self.verificationErrors)
if __name__=="__main__":
unittest.main()

test_case.py内容如下:

import os
caselist=os.listdir("D:\\unittest\\test_case")
for a in caselist:
s=a.split('.')[-1]
if s=='py':
os.system('python D:\\unittest\\test_case\\%s 1>>log.txt 1>>&1'%a)

caselist获取test_case目录下所有的文件列表做成列表
"baidu.py"为列表的一个元素,用split以"."分割为2个字符串,[-1]表示取出后面py的字符串,判断是否是测试用例文件。如果是用os.system执行DOS命令,DOS命令为执行测试用例并把日志打印到log.txt

至此,一个简单的单元自动化测试框架实现完毕。

生成HTMLTestRunner测试报告

HTMLTestRunner是Python标准库的unittest模块的一个扩展。它能生成易于使用的HTML报告。
1、下载HTMLTestRunner.py文件:
地址:HTMLTestRunner - tungwaiyip's software
2、将该文件保存在python安装路径下的lib文件夹中。在文件中能import HTMLTestRunner成功,即配置成功。
注:如果失败,在项目中新建一个这样的文件也是可以的,只要达到能引入和使用就行。

针对baidu.py文件修改如下:
import HTMLTestRunner #导入HTMLTestRunner报告

if __name__=="__main__":
suite=unittest.TestSuite() #构建测试套件
suite.addTest(baidu("test_baidu_set")) #添加测试用例到套件
filename='D:\\unittest\\test_case\\result.html' #建立HTML报告文件
fp=file(filename,'wb')
runner=HTMLTestRunner.HTMLTestRunner(stream=fp,title=u"百度知道测试报告",description=u"用例执行情况:") #自定义测试报告
runner.run(suite) #运行测试用例

说明:
使用HTMLTestRunner配置参数,stream为输出报告路径、title为报告标题、description为描述信息

不同文件中的用例构建测试套件

生成HTMLTestRUnner测试报告的时候,会发现一个.py文件产生一个测试报告,那么对于测试用例的多个测试用例文件会产生多个测试报告,这在实际中会导致测试报告不方便阅读;如果把所有测试用例写在一个py文件里,会导致用例不好维护。
之前使用TestSuite只是在一个.py文件里添加多个测试用例,我们可以跨文件通过TestSuite来组织测试用例。

import unittest #导入单元测试框架
import asearch,baidu #导入测试用例py文件
alltest=unittest.TestSuite() #构建测试套件
alltest.addTest(unittest.makeSuite(asearch.search)) #增加测试用例集
alltest.addTest(unittest.makeSuite(baidu.baidu)) #增加测试用例集
runner=unittest.TextTestRunner(verbosity=2)
runner.run(alltest) #运行测试

makeSuite用于生产testsuite对象的实例,把所有的测试用例组装成TestSuite,最后把TestSuite传给TestRunner执行。

测试套件的语法:

suite1=module1.TheTestSuite()
suite2=module2.TheTestSuite()
alltests=unittest.TestSuite((suite1,suite2))

整合测试报告

import unittest #导入单元测试框架
import asearch,baidu #导入测试用例py文件
import HTMLTestRunner
alltest=unittest.TestSuite() #构建测试套件
alltest.addTest(unittest.makeSuite(asearch.search)) #增加测试用例集
alltest.addTest(unittest.makeSuite(baidu.baidu)) #增加测试用例集
filename="D:\\unittest\\test_case\\result.html"
fp=file(filename,'wb')
runner=HTMLTestRunner.HTMLTestRunner(stream=fp,title=u"百度知道测试报告",description=u"执行测试用例情况test:")
runner.run(alltest)

备注:unittest.makeSuite()是直接从一个TestCase的类生成一个TestSuite。
测试套件运行每个测试用例的顺序是由测试方法名根据Python内建函数cmp所排序的顺序而决定的。

测试报告再优化

1、易读性优化:
可以给每个测试用例加上注释:

def test_baidu_self(self):
"""百度知道链接测试"""
driver=self.driver
driver.maximize_window()

2、测试报告名称加时间戳:
引入python的time模块给测试报告加自定义名称

import time
#生成格式化时间
now=time.strftime("%Y_%m_%d_%H_%M_%S",time.localtime(time.time()))
#now=time.strftime("%Y_%m_%d_%H_%M_%S")
#采取字符串拼接给测试报告加时间
filename='D:\\unittest\\test_case'+now+'_result.html'
#生成的报告文件名格式为:2019_01_01_10_30_59_result.html

框架结构改进

1、执行用例的主文件移出测试用例文件夹
执行用例的主文件是执行所有测试用例的程序,而并非是测试用例本身,把它移出来结构更为合理。
移出来执行用例的主文件后,会发现import测试用例类报错,这是需要使用python包。

在test_case下新建一个__init__.py的文件,文件内容可以为空,同时将test_case目录添加到sys.path中

import sys
sys.path.append("\test_case")
from test_case import baidu,asearch

sys.path.append 把test_case目录添加到path下,使用的是相对路径
当执行用例的主文件和测试用例放置在同一目录下,可以直接调用;移除出来之后就找不到模块了;python查找模块是先从当前目录查找,如果找不到再从安装python安装设置的相关路径下去查找,这里使用sys.path目的就是设置路径。
为了标识一个目录是可引用的包,需要在目录下创建__init__.py的文件

2、init.py文件的使用:
一个Python包是一个带有特殊文件__init__.py的目录。init.py文件定义了包的属性和方法,它控制着包的导入行为。
假如__init__.py为空,那么仅仅导入包是什么都做不了的。

对于from test_case import,asearch 把baidu和asearch文件导入到测试用例执行主文件,也可以在__init__.py文件实现导入:
在__init__.py中添加如下内容

import baidu
import asearch

然后在测试用例执行主文件中,可以如下方式调用baidu 和asearch文件:

from test_case import *

这样也跟编写from test_case import baidu,asearch的作用是一致的。

3、把用例中的公共模块移出
首先在test_case目录下创建一个Public 目录,然后把一些公共的模块用函数或者类实现,比如:登录模块,退出模块,同时把测试用例做相应修改

sys.path.append("\public")
from public import login
login.login()

用例读取改进

在实际过程中,可能我们需要组织成百上千条测试用例,虽然我们可以通过导入包文件的方式添加测试用例,但每创建一个新的测试用例都需要在测试套件中增加一条add Test语句,随着用例的增加,不便于管理和维护。

解决方法一:
首先把用例文件组装称为一个数组,然后使用for循环遍历的方式读取测试用例文件。示例如下:

testnames=[asearch.search,baidu.baidu]
alltest=unittest.TestSuite() #构建测试套件
for testname in testnames:
alltest.addTest(unittest.makeSuite(testname)) #增加测试用例集

然后,为了在增添或者删除测试用例时不必对执行测试用例的主文件做任何修改,可以把用例列表放置到一个单独的文件中通过执行测试用例的主文件导入。

解决方法二:
使用discover解决用例的读取。在使用解决方法一for循环处理用例读取的时候,如果新增测试用例文件testa.py,那么需要在__init__.py文件中编写import testa,还需要在测试用例列表文件的数据中增加相应测试用例名称,这样才能使新的测试用例添加到测试套件中执行,这样做显然不方便。
对于上述情况,可以使用TestLoader(测试用例加载器)中的加载多个测试用例方法来实现。

discover函数原型如下:
discover(start_dir,pattern='test*.py',top_level_dir=None)

递归查找指定目录(start_dir)及其子目录下的全部测试模块,将这些测试模块放入一个TestSuite对象并返回。只有匹配pattern的测试文件才会被加载到TestSuite中。
如果一个测试文件的名称符合pattern,将检查该文件是否包含load_tests()函数,如果load_tests()函数存在,则由该函数负责加载本文件中的测试用例。如果不存在,就会执行loadTestsFromModule(),查找该文件中派生自TestCase的类包含的test开头的方法。
top_level_dir=None:测试模块的顶级目录(指测试用例不是放在多级目录中),如果没有顶级目录,默认为空。

test_lists="D:\\unittest\\test_case"
def creatsuite():
testunit=unittest.TestSuite()
discover=unittest.defaultTestLoader.discover(
test_lists,
pattern='ok_*.py',
top_level_dir=None
)
for testsu in discover:
for testcase in testsu:
testunit.addTests(testcase)
return testunit
testnames=createsuite()
runner.run(testnames)

在实际测试用例开发过程中,我们可以使用约定,写好的用例用ok_或者其他标识开头,没有写好的暂时不用ok_开头。不然会影响测试用例的执行。

简单框架目录结构

  • unittest
    • test_case
      • ok_baidu.py 测试用例
      • ok_search.py 测试用例
      • init.py
      • Public
        • login.py 公共模块
        • quit.py 公共模块
        • init.py
    • report
      • time_result.html 测试报告
        -data
      • userinfo.csv 参数化文件
    • test_all.py 执行所有用例
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/xiao1542/article/details/127854743

智能推荐

【张兔兔送书第一期:考研必备书单】-程序员宅基地

文章浏览阅读4k次,点赞55次,收藏55次。八九月的朋友圈刮起了一股晒通知书潮,频频有大佬晒出“研究生入学通知书”,看着让人既羡慕又焦虑。果然应了那句老话——比你优秀的人,还比你努力。

链路追踪php,easyswoole链路追踪-程序员宅基地

文章浏览阅读543次。TrackerEasyswoole提供了一个基础的追踪组件,方便用户实现基础的服务器状态监控,与调用链记录。组件要求php: >=7.1.0ext-swoole: ^4.4.0easyswoole/component: ^2.0安装方法composer require easyswoole/tracker仓库地址调用链Easyswoole的调用链跟踪是一个以类似有序的树状链表的解构实现的,解..._php 链路追踪

【云计算概念】IaaS、PaaS、SaaS、CaaS、MaaS的区别-程序员宅基地

文章浏览阅读2.8k次,点赞2次,收藏3次。五者之间主要的区别在于第一个单词,而都是(即服务)的意思,这五者都是云计算的落地产品。

下班的时候在电梯里碰见个妹子,问这层楼是哪个部门的。我答技术部吧。她惊异:技术部也这么晚下班?妹子,你听说过科比和程序员的故事么?-程序员宅基地

文章浏览阅读1.4k次。下班的时候在电梯里碰见个妹子,问这层楼是哪个部门的。我答技术部吧。她惊异:技术部也这么晚下班?妹子,你听说过科比和程序员的故事么?转自:程序猿才懂得笑话 http://cxmonkey.duapp.com/?p=222

【0day】复现用友 NC NCFindWeb大型企业数字化平台log4j远程代码执行漏洞-程序员宅基地

文章浏览阅读220次。NC是一款企业级ERP软件。作为一种信息化管理工具,用友NC提供了一系列业务管理模块,包括财务会计、采购管理、销售管理、物料管理、生产计划和人力资源管理等,帮助企业实现数字化转型和高效管理。用友 NC NCFindWeb大型企业数字化平台存在log4j远程代码执行漏洞,攻击者可以在恶意环境变量中插入特定的代码,使得Log4j执行该代码。_用友 nc ncfindweb大型企业数字化平台log4j远程代码执行漏洞

MySQL数据库分卷备份还原类_sql数据库分卷备份和还原-程序员宅基地

文章浏览阅读101次。执行数据库恢复是DBA的日常生活的一部分。一个DBA可能需要执行恢复由于种种原因,如恢复,刷新数据库用于测试目的等许多倍,它可能很难执行恢复由于损坏的媒体,在服务器上的磁盘空间不足等。在这篇文章中,我将概述的方法之一,我用来恢复的备份生产数据库的方案夫妇的日子,我的支持团队的成员来找我,说他们是无法刷新农行从生产服务器相同的的备份副本名为OLTP开发环境数据库。从生产服务器的备份副本大约75 GB...

随便推点

insert into插入数据_insert into `tjgxck`.`inv_nova_inv_mininv` (`id`, -程序员宅基地

文章浏览阅读3.8k次。insert into 表名称 values(值1,值2,...) 可以指定所要插入数据的列:INSERT INTO table_name (列1, 列2,...) VALUES (值1, 值2,....)遇到了个自己都想不到的错误,我一定是没睡醒_insert into `tjgxck`.`inv_nova_inv_mininv` (`id`, `bizorgcode`, `createddate

自己封装一个文件上传函数(使用axios)-程序员宅基地

文章浏览阅读187次。  昨天在写博客的个人动态页面,里面涉及到了图片上传。  之前我都是用的别人的插件和elementUI的upload组件。但是现在没法用了。  页面的效果差别有点大,如果改elementUI的样式,会很累。  这时候很愁人啊。(懒啊!)  这是页面开发的效果,很像qq空间的感觉。  ...

排课系统算法_排课算法-程序员宅基地

文章浏览阅读1.4k次,点赞3次,收藏22次。业务上需要做一个排课系统,先调研了业内友商的排课系统,同时做了算法的对比和可行性分析。_排课算法

SHA算法系列介绍-程序员宅基地

文章浏览阅读1.8w次,点赞8次,收藏27次。我们先来回顾一下MD5算法的核心过程,简而言之,MD5把128bit的信息摘要分成A,B,C,D四段(Words),每段32bit,在循环过程中交替运算A,B,C,D,最终组成128bit的摘要结果。老师:SHA-2的子版本包括SHA-224,SHA-256,SHA-384,SHA-512。再看一下SHA-1算法,核心过程大同小异,主要的不同点是把160bit的信息摘要分成了A,B,C,D,E五段。再看一下SHA-2系列算法,核心过程更复杂一些,把信息摘要分成了A,B,C,D,E,F,G,H八段。_sha算法

ADC模数转换-基于STM32F407-独立模式-单通道中断模式-配置_stm32f407 单通道adc 库函数-程序员宅基地

文章浏览阅读642次。// ADC GPIO 宏定义#define RHEOSTAT_ADC_GPIO_PORT GPIOB#define RHEOSTAT_ADC_GPIO_PIN GPIO_Pin_0#define RHEOSTAT_ADC_GPIO_CLK RCC_AHB1Periph_GPIOB// ADC 序号宏定义#define RHEOSTAT_ADC ADC1#define RHEOSTAT_ADC_CLK RCC_APB2Peri._stm32f407 单通道adc 库函数

小波变换:小波基函数_小波变换dbn-程序员宅基地

文章浏览阅读4.9k次。与标准的傅里叶变换相比,小波分析中使用到的小波函数具有不唯一性,即小波函数具有多样性。小波分析在工程应用中,一个十分重要的问题就是最优小波基的选择问题,因为用不同的小波基分析同一个问题会产生不同的结果。目前我们主要是通过用小波分析方法处理信号的结果与理论结果的误差来判定小波基的好坏,由此决定小波基。常用小波基有Haar 小波、Daubechies(dbN) 小波、Mexican Hat(mexh) 小波、Morlet 小波、Meyer 小波等。..._小波变换dbn

推荐文章

热门文章

相关标签