自动化测试框架-----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

智能推荐

1024 分辨率下最快模型,字节跳动文生图开放模型 SDXL-Lightning 发布_sdxllightning下载-程序员宅基地

文章浏览阅读1k次,点赞28次,收藏25次。很高兴跟大家分享我们最新的文生图模型 —— SDXL-Lightning,它实现了前所未有的速度和质量,并且已经向社区开放。_sdxllightning下载

关于cloacked-pixel的一些总结_03-cloacked-pixel-程序员宅基地

文章浏览阅读1.5k次,点赞2次,收藏2次。前两天遇到一道lsb隐写的题目,需要用到cloacked-pixel这个脚本。工具地址下载后解压即可,这里需要注意,该脚本是基于python2的!但我电脑anaconda里面只有python3并没有很好的python基础,但借助anaconda可以轻松解决很多问题!教程如下:anaconda中添加python2但是在运行脚本时还会提示缺库(注意运行时要activate pythonXX手动切换到你所配置的python2环境下)继续使用anaconda为python2装缺少的._03-cloacked-pixel

WPF DataGrid添加右键菜单-程序员宅基地

文章浏览阅读2k次,点赞2次,收藏2次。原文http://home.cnblogs.com/group/topic/54788.html用代码添加:View Code <DataGrid.ContextMenu> <ContextMenu Name="dgmenu1" StaysOpen="true"> &..._wpf datagrid右键菜单

EB Tresos Studio离线激活方法_ebtresos离线激活-程序员宅基地

文章浏览阅读1.9k次,点赞2次,收藏6次。EB Tresos Studio离线激活方法_ebtresos离线激活

B端系统-权限管理_如何将后台管理系统b端化-程序员宅基地

文章浏览阅读95次。当然用户组是可以拓展的,部门和职位常用在内部的管理系统,如果是面向c端的系统,比如淘宝网的商家,商家自身也有一套组织架构,如采购部,销售部,客服部,后勤部,有些人拥有客服权限,有些人拥有上架权限等,这就体现了用户组的扩展性。关于数据权限的处理,常见的有两种方式,一种是在角色内完成数据权限的定义,另一种是将角色和权限分开,两种方式各有偏重。即页面的功能按钮,包括查看,新增,修改,删除,审核等,用户点击删除按钮时,后台会校验用户角色下用户的所有权限是否包含该删除权限,如果是,就可以下一步,反之提示无权限。_如何将后台管理系统b端化

音乐节拍提取一-程序员宅基地

文章浏览阅读601次。前段时间倒腾了一下音乐节拍数检测,参考下面的网上的一个测试歌曲列表做了下对比,效果还不错,基本上都是准的。Itunes LinkNameTimeArtistBPMAlbumGenreAmazon LinkLoneliest Soul03:35Grace Potter and the Nocturnals168The Lion The Bea..._提取音乐节奏

随便推点

win11实时字幕无法下载问题_微软实时字幕下载不了-程序员宅基地

文章浏览阅读776次。一直卡着的话直接去Microsoft Store下载这个试试看。_微软实时字幕下载不了

如何让谷歌Chrome地址栏恢复显示“www”和“https://”标识符_chrome 地址显示原始-程序员宅基地

文章浏览阅读1.1w次。如何让谷歌Chrome地址栏恢复显示“www”和“https://”标识符地址栏隐藏“www”和“https://”标识符  谷歌 Chrome 现在默认在所有网站地址栏中少了一些内容,“www”子域和“https://”被隐藏起来了,因为谷歌认为这些不是大多数人要关注的信息。  Chrome 的产品经理 Emily Schechter 说,他们将开始从桌面版和 Android 版的第..._chrome 地址显示原始

目标跟踪数据集整理(四)----TColor-128(Temple Color 128)_encoding color information for visual tracking: al-程序员宅基地

文章浏览阅读3.5k次。文章目录Encoding Color Information for Visual Tracking:Algorithms and Benchmark 2015官网 下载数据集(4.4G)本文认为颜色信息可以提供丰富的判别线索对于视觉推理,大多数现代视觉跟踪器限制在灰度域。(也就是主要解决输入序列是灰度版本)因此我们在算法和基准两方面做了系统的研究,证明了颜色信息可以帮助提升视觉跟踪效果。..._encoding color information for visual tracking: algorithms and benchmark

论文解读--Visual Lane Tracking and Prediction for Autonomous Vehicles-程序员宅基地

文章浏览阅读860次,点赞20次,收藏21次。我们提出了一种用于自动驾驶汽车跟踪水平道路车道标记位置的可视化方法。我们的方法是基于预测滤波的。预测步骤估计在每个新的图像帧中期望的车道标记位置。它也是基于汽车的运动学模型和嵌入式测程传感器产生的信息。使用适当准备的测试车辆获得的实验结果表明,在某些条件下,如振荡和变道,预测步骤可以显著地减少跟踪误差。因此,我们相信我们的方法应用于基于图像的控制自动驾驶汽车可以提高系统性能。

sap 标准委外和工序委外_委外加工SAP的两种典型委外处理方法-程序员宅基地

文章浏览阅读1.2k次。通常提供两种基本处理方式:外包采购和工序外包。生产外包经营方式简介生产外包作为一种全新的生产经营方式,改善了传统方式的不足,主要类型有:一.OEM:(OrignalEquipmentManufactuce->原始设备制造商)典型的OEM方式为:拥有原始设备的OEM加工方(受委托方)按照委托方的要求,用自己的设备为其加工生产产品,而后贴上委托方商标交货,整个活动中,加工方只获得加工费用,自..._工序委外加工属于什么变更类型

yolov3算法详解_2020年阿里-算法工程师面经-程序员宅基地

文章浏览阅读432次。写在前面:暑期实习从申请到拿到阿里意向书大概持续了1个月的时间,和周围其他同学比较,我的面试流程算走的比较快的了。还没有拿到意向书的朋友们也不要太着急,调整好心态好好准备(虽然内心多多少少会有些焦虑),阿里走流程算是比较快的了。希望能对求职的你有所帮助。【阿里云1面(算法实习生)】1、自我介绍、项目介绍2、死锁出现的原因以及如何避免雾夜飞鹰:死锁产生的原因及四个必要条件​zhuanlan.zhih..._yolo模型的时间复杂度