一、概念
UnitTest是Python标准库中自带的一个模块,类似于Java中的Junit单元测试框架,其模块提供了许多类和方法处理各种测试工作,能够完善结合Selenium、Appium、Request等实现UI自动化与接口自动化。
在学习之前,我们首先要了解几个概念:
–TestCase:测试用例,一个完整的测试流程就是一个测试用例,通过一些特定的输入得到相应,并对结果进行校验的过程,所有的用例都是直接继承于unitTest.TestCase
类,TestCase是最小的测试单元,具有独立性。
–TestFixture:测试固件,在执行测试之前的准备工作,比如数据清理、创建临时数据库、目录、以及开启某些服务进程。在编写测试代码时,总会有一些重复的代码部分,比如测试一个网站的登录操作时,简单分为三个用例:账号和密码都正确,账号正确密码错误,账号错误密码正确,这三种情况在执行用例时都需要首先访问系统地址,再输入账号和密码,点击登录操作,完成之后浏览器执行关闭操作,我们就可以通过setUp()
将访问地址作为前置条件,通过tearDown()
将关闭浏览器作为后置条件。测试固件就是整合了代码的公共部分。
–TestSuite:测试套件,把多个测试用例集合到一起,而测试套件和测试用例一样,也可以有多个,并且可以组合在一起形成更多的测试用例集合。
–TestRunner:测试运行器,提供测试用例运行环境,通过run()方法来执行测试用例,并在执行完成后将测试结果输出。unittest框架的TextTextRunner()类,通过该类下面的run()方法来运行suite所组装的测试用例,入参为suite测试套件。
二、UnitTest环境搭建
Python安装时就已经默认封装好了UnitTest框架,调用框架的时候只需要import unittest
即可。
三、小试牛刀
# 导包
import unittest
class UnitForTest(unittest.TestCase):
# 前置条件
def setUp(self) -> None:
print('this is setUp')
# 后置条件
def tearDown(self) -> None:
print('this is tearDown')
# 定义测试用例
def test_1(self):
print('this is test1!!!')
def test_3(self):
print('this is test3!!!')
def test_2(self):
print('this is test2!!!')
if __name__ == '__main__':
unittest.main()
运行结果:
如上图运行结果所示,setUp()会在每个单独的测试用例运行之前都执行一次,tearDown()会在每个单独的测试用例运行之后都执行一次。
与setUp()和tearDown()类似的方法还有setUpClass()和tearDownClass(),setUpClass()在每个类执行前调用一次,tearDownClass()在每个类执行后调用一次,使用这两个方法时必须加 @classmethod
装饰器。
UnitTest的语法规则:
(1) UnitTest中,所有的用例类都是直接继承于unitTest.TestCase
类;
(2)UnitTest中,测试用例的定义都是以test_
开头;
(3)用例的运行顺序与代码中编写的顺序无关,运行顺序遵循A-Z,a-z,0-9;
(4)单个测试类运行时必须有 unittest.main()
方法。
四、TestSuite-测试套件
首先新建一个UnitTest类,编写测试用例,文件名为unit_for_testA.py,代码如下:
# 导包
import unittest
class UnitForTestA(unittest.TestCase):
# 前置条件
def setUp(self) -> None:
print('this is AsetUp')
# 后置条件
def tearDown(self) -> None:
print('this is AtearDown')
# 定义测试用例
def test_1(self):
print('this is Atest1!!!')
def test_2(self):
print('this is Aest2!!!')
def test_3(self):
print('this is Atest3!!!')
再新建一个类存放套件,文件名为TestSuite_demo.py,直接在UnitTest类中运行无法生效。
下面创建一个测试套件(unittest.TestSuite()
),并分别用五种不同的方法给该测试套件添加测试用例。
(1)添加测试用例的第一种方法–单个添加(addTest
)
# 导入unittest的包
import unittest
# 导入存放测试用例的类
from unittest_demo.unit_for_testA import UnitForTestA
# 创建一个测试套件
suite = unittest.TestSuite()
# 添加测试用例的第一种方法
suite.addTest(UnitForTestA('test_1'))
suite.addTest(UnitForTestA('test_2'))
suite.addTest(UnitForTestA('test_3'))
# 基于Ruunner来运行测试套件
runner = unittest.TextTestRunner()
runner.run(suite)
(2)添加测试用例的第二种方法–批量添加(addTests
)
# 导入unittest的包
import unittest
# 导入存放测试用例的类
from unittest_demo.unit_for_testA import UnitForTestA
# 创建一个测试套件
suite = unittest.TestSuite()
# 添加测试用例的第二种方法
cases = [UnitForTestA('test_1'), UnitForTestA('test_2'), UnitForTestA('test_3')]
suite.addTests(cases)
# 基于Ruunner来运行测试套件
runner = unittest.TextTestRunner()
runner.run(suite)
(3)添加测试用例的第三种方法–根据路径下的文件名匹配(defaultTestLoader.discover
)
该添加测试用例的方法也为批量执行,代码中指定的目录下,如果文件名称满足筛选条件,则所有满足条件的文件中的所有测试用例都会被执行,为了看清效果,我们再建一个UnitTest类,编写测试用例,文件名为unit_for_testB.py,代码如下:
# 导包
import unittest
class UnitForTestB(unittest.TestCase):
# 前置条件
def setUp(self) -> None:
print('this is BsetUp')
# 后置条件
def tearDown(self) -> None:
print('this is BtearDown')
# 定义测试用例
def test_1(self):
print('this is Btest1!!!')
def test_2(self):
print('this is Best2!!!')
def test_3(self):
print('this is Btest3!!!')
目录结构如图
此时在测试套件类(TestSuite_demo.py)中,根据路径下的文件名添加测试用例的代码为:
# 导入unittest的包
import unittest
# 创建一个测试套件
suite = unittest.TestSuite()
# 添加测试用例的第三种方法
test_dir = './' # 指定的路径下所有与文件名相匹配的文件下的测试用例(批量执行)
discover = unittest.defaultTestLoader.discover(start_dir=test_dir, pattern='unit_for_test*.py')
# 基于Ruunner来运行测试套件
runner = unittest.TextTestRunner()
runner.run(discover)
运行结果如下图,由图可见,在当前目录下的两个文件中的用例都执行了。
start_dir
:需要执行的文件路径;
pattern
:需要匹配的文件名称,其中unit_for_test*.py
匹配所有以unit_for_test开头的python文件;
(4)添加测试用例的第四种方法–根据类名去读取(单个类)(TestLoader().loadTestsFromTestCase()
)
# 导入unittest的包
import unittest
# 导入存放测试用例的类
from unittest_demo.unit_for_testA import UnitForTestA
# 创建一个测试套件
suite = unittest.TestSuite()
# 添加测试用例的第四种方法
suite.addTests(unittest.TestLoader().loadTestsFromTestCase(UnitForTestA))
# 基于Ruunner来运行测试套件
runner = unittest.TextTestRunner()
runner.run(suite)
(5)添加测试用例的第五种方法–根据名称去添加(TestLoader().loadTestsFromName()
)
# 导入unittest的包
import unittest
# 创建一个测试套件
suite = unittest.TestSuite()
# 添加测试用例的第五种方法
suite.addTests(unittest.TestLoader().loadTestsFromName('unit_for_testA.UnitForTestA'))
# 基于Ruunner来运行测试套件
runner = unittest.TextTestRunner()
runner.run(suite)
五、跳过用例
在执行用例的时候,不是每个用例都必须跑一次,unittest提供了跳过一些跳过用例不执行的方法,在用例前加装饰器,具体有:
(1)@unittest.skip:直接跳过本条用例不执行;
(2)@unittest.skipUnless(1 > 2, ‘1>2是假的’):当条件为假时,跳过该用例不执行;
(3)@unittest.skipIf(1 < 2, ‘1<2是真的’):当条件为真时,跳过该用例不执行。
六、数据驱动DDT(Data Driven Tests)
在介绍数据驱动之前,我们先来看一下下面这个示例,代码的内容特别特别的简单,用来模拟自动化测试时输入用户名和密码(哈哈,我知道这样模拟有点勉强!!!):
1、打开百度;
2、第一次在输入框中输入java,然后清除,再输入123456,关闭浏览器;
3、第二次在输入框中输入selenium,然后清除,输入abcdef,关闭浏览器;
import unittest
from selenium import webdriver
import time
class UnitForTestddt(unittest.TestCase):
def setUp(self) -> None:
self.driver = webdriver.Chrome()
self.driver.get('http://www.baidu.com')
def test_ddt1(self):
self.driver.find_element_by_id('kw').send_keys('java')
time.sleep(1)
self.driver.find_element_by_id('kw').clear()
time.sleep(1)
self.driver.find_element_by_id('kw').send_keys('123456')
time.sleep(1)
def test_ddt2(self):
self.driver.find_element_by_id('kw').send_keys('selenium')
time.sleep(1)
self.driver.find_element_by_id('kw').clear()
time.sleep(1)
self.driver.find_element_by_id('kw').send_keys('abcdef')
time.sleep(1)
def tearDown(self) -> None:
self.driver.quit()
if __name__ == '__main__':
unittest.main()
由代码可以看出,中间的测试用例部分很多代码是重复的,造成代码冗余,为了解决代码冗余,我们采用数据驱动的方式;同时,为了方便后期代码的维护,进行数据与代码的分离。UnitTest没有自带数据的驱动功能,如果在使用UnitTest的同时又想使用数据驱动,那么就可以使用DDT来完成。
使用方法如下:
(1)ddt.data
:装饰测试方法,参数是一系列的值,比如元组等;
(2)ddt.file_data
:装饰测试方法,参数是文件名,测试数据保存在参数文件中。文件类型可以是JSON或者YAML;
(3)ddt.unpack
:当ddt传递复杂的数据结构时使用,通常称为解包。
下面分别将这几个方法进行示例演示,在使用ddt之前我们需要导包:import ddt
1. ddt.data方法
import unittest
from selenium import webdriver
import time
import ddt
@ddt.ddt
class UnitForTestddt(unittest.TestCase):
def setUp(self) -> None:
self.driver = webdriver.Chrome()
self.driver.get('http://www.baidu.com')
@ddt.data('java', 'selenium', 'python')
def test_ddt(self, a):
self.driver.find_element_by_id('kw').send_keys(a)
time.sleep(1)
def tearDown(self) -> None:
self.driver.quit()
if __name__ == '__main__':
unittest.main()
以上示例中,传递数据的格式为元组,元组中包含了三个元素,浏览器共打开了三次,而三次中分别输入了java、selenium、python,很显然可以看出,代码中只编写了一次测试用例,而实际结果中却执行了三次,减少了代码的冗余,实现了不同的输入条件执行相同的测试用例。
ddt.data
在使用过程中,需要注意一下几点:
(1)导包:import ddt
;
(2)在测试类之前加装饰方法:@ddt.ddt
;
(3)在测试用例之前加 @ddt.data()
,传入数据;
2. ddt.unpack方法
我们再看一个示例,如下:
import unittest
from selenium import webdriver
import time
import ddt
@ddt.ddt
class UnitForTestddt(unittest.TestCase):
def setUp(self) -> None:
self.driver = webdriver.Chrome()
self.driver.get('http://www.baidu.com')
@ddt.data(['java', '123456'], ['python', '666666'])
@ddt.unpack
def test_ddt1(self, username, pwd):
self.driver.find_element_by_id('kw').send_keys(username)
time.sleep(1)
self.driver.find_element_by_id('kw').clear()
time.sleep(1)
self.driver.find_element_by_id('kw').send_keys(pwd)
time.sleep(1)
def tearDown(self) -> None:
self.driver.quit()
if __name__ == '__main__':
unittest.main()
当我们传递的内容为list列表时,需要添加@ddt.unpack
进行解包,否则会运行错误,示例中传递了两个list,用例执行了两次,分别将java、python传递给了username,将123456,666666传递给了pwd。
3. 从文件中读取数据
在实际项目中,一般测试数据都比较多,测试数据都会写在文件中,下面我们以.txt文件为例,介绍一下如何从文件中读取数据。
在当前包下新建一个testdata.txt文件,文件的内容如下:
java,123456
python,666666
selenium,777777
现在在测试用例中读取testdata.txt中的数据,代码如下:
import unittest
from selenium import webdriver
import time
import ddt
@ddt.ddt
class UnitForTestddt(unittest.TestCase):
# 读取文件内容
def read_file():
file = open('testdata.txt', 'r', encoding='utf-8')
li = []
for line in file.readlines():
li.append(line.strip('\n').split(','))
file.close()
return li
def setUp(self) -> None:
self.driver = webdriver.Chrome()
self.driver.get('http://www.baidu.com')
# 一个*表示以元祖的形式去解读,两个*表示以字典的形式去解读
@ddt.data(*read_file())
@ddt.unpack
def test_ddt1(self, username, pwd):
self.driver.find_element_by_id('kw').send_keys(username)
time.sleep(1)
self.driver.find_element_by_id('kw').clear()
time.sleep(1)
self.driver.find_element_by_id('kw').send_keys(pwd)
time.sleep(1)
def tearDown(self) -> None:
self.driver.quit()
if __name__ == '__main__':
unittest.main()
以上读取txt文件的方式,是基于Python中open方法、readlines方法等原本的处理方式去读取,并未实现完全的兼容,而ddt中直接兼容了yaml文件的读取。
4. 从YAML文件中读取数据(file_data)
首先yaml的安装:pip install pyyaml
在当前包下新建一个testdata.yaml文件,文件的内容(编写时注意格式)如下:
-
username: java
pwd: 123456
-
username: python
pwd: 666666
-
username: selenium
pwd: 777777
现在在测试用例中读取testdata.yaml中的数据,代码如下:
import unittest
from selenium import webdriver
import time
import ddt
@ddt.ddt
class UnitForTestddt(unittest.TestCase):
def setUp(self) -> None:
self.driver = webdriver.Chrome()
self.driver.get('http://www.baidu.com')
# 读取文件中的数据
@ddt.file_data('testdata.yaml')
def test_ddt(self,**user):
self.driver.find_element_by_id('kw').send_keys(user.get('username'))
time.sleep(1)
self.driver.find_element_by_id('kw').clear()
time.sleep(1)
self.driver.find_element_by_id('kw').send_keys(user.get('pwd'))
time.sleep(1)
def tearDown(self) -> None:
self.driver.quit()
if __name__ == '__main__':
unittest.main()
七、UnitTest生成测试报告(HTMLTestRunner)
批量执行完用例后,生成的测试报告是文本形式的,不够直观,为了更好的展示测试报告,最好是生成HTML格式的。unittest里面是不能生成html格式报告的,需要导入一个第三方的模块:HTMLTestRunner
环境搭建
(1)下载HTMLTestRunner.py,导入到Python中的lib文件夹下;
(2)修改部分源码
由于HTMLTestRunner.py是基于python2开发,为了使其支持python3的环境,需要对其内容进行部分修改
#第94行
import StringIO 修改为:import io
#第539行
self.outputBuffer = StringIO.StringIO() 修改为:self.outputBuffer = io.StringIO()
#第631行
print >>sys.stderr, '\nTime Elapsed: %s' % (self.stopTime-self.startTime)修改为:print(sys.stderr, '\nTime Elapsed: %s' % (self.stopTime-self.startTime))
#第642行
if not rmap.has_key(cls):修改为:if not cls in rmap:
#第766行
uo = o.decode('latin-1')修改为:uo = o
#第772行
ue = e.decode('latin-1')修改为:ue = e
(3)导包:from HTMLTestRunner import HTMLTestRunner
示例代码如下:
# 导入unittest的包
import unittest
# 导入测试类
from unittest_demo.unit_for_testA import UnitForTestA
# 导入生成报告所需要的包
from HTMLTestRunner import HTMLTestRunner
import os
# 创建一个测试套件
suite = unittest.TestSuite()
# 添加测试用例的第二种方法
cases = [UnitForTestA('test_1'), UnitForTestA('test_2'), UnitForTestA('test_3')]
# 集成测试报告
report_name = '测试报告名称'
report_title = '测试报告标题'
report_desc = '测试报告描述'
report_path = './report/'
report_file = report_path + 'report.html'
if not os.path.exists(report_path):
os.mkdir(report_path)
else:
pass
with open(report_file, 'wb') as report:
suite.addTests(cases)
runner = HTMLTestRunner(stream=report, title=report_title, description=report_desc)
runner.run(suite)
执行完成后,在当前的目录下会生成一个report/report.html文件,通过浏览器打开,界面如下:
八、后记
以上是作为一个测试小白关于python中UnitTest的理解,烦请各位大佬们不吝赐教,在软件测试方面我还有很多很多知识要学习,希望未来一起加油!!!
——-本文内容不用于商业目的,如涉及知识产权问题,请随时联系!!!——-
—————–祝福所有人学有所成,工作顺利,万事如意!!!—————-
今天的文章简单聊一聊关于UnitTest分享到此就结束了,感谢您的阅读。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/13733.html