自动化测试框架Unittest
unittest框架 这个框架是python的黑盒测试的框架 注意区分一个容易混淆的。Junit是java白盒测试的框架。 unittest是如何体现出黑盒测试的?它是web界面的功能测试的框架 unittest 单元测试提供了创建测试用例,测试套件以及批量执行的方案, unittest 在安装pyhton 以后就直接自带了,直接import unittest 就可以使用。 各个组件关系图
Test Fixture (测试固件)
里面有很多固定的方法,初始化和清理测试环境,比如创建临时的数据库,文件和目录等。其中setup() 和 setDown()是最常用的方法 任何继承自unnitTest类里面的而固定方法 setUp():初始化方法 tearDwon():清理工作 每执行一个test_方法 都会对应重新执行setUp和tearDown()
TestCase (测试用例)
单元测试用例,TestCase是编写单元测试用例最常用的类 每一个test_ 开头的方法 都可以叫做测试用例 运行tetst_ 开头所在的类的时候都会默认执行,但是这个类里面的其他方法需要显示的手动调用执行
代码演示测试固件和测试用例
from selenium import webdriver
import unittest
import time
import os
class TestCase1(unittest.TestCase):
def setUp(self):
self.driver = webdriver.Chrome()
self.url = "https://www.baidu.com/"
self.driver.get(self.url)
self.driver.maximize_window()
time.sleep(2)
def tearDown(self):
self.driver.quit()
def test_baidu1(self):
self.driver.find_element_by_id("kw").send_keys("张杰巡回演唱会")
self.driver.find_element_by_id("su").click()
time.sleep(2)
def test_baidu2(self):
self.driver.find_element_by_link_text("新闻").click()
time.sleep(3)
if __name__ == "__main__":
unittest.main(verbosity=1)
关于verbosity 的参数解释
可以增加verbosity参数,例如unittest.main(verbosity=2) 在主函数中,直接调用main() ,在main中加入verbosity=2 ,这样测试的结果就会显示的更加详细。 这里的verbosity 是一个选项,表示测试结果的信息复杂度,有三个值 0 ( 静默模式): 你只能获得总的测试用例数和总的结果比如总共100个失败,20 成功80 1 ( 默认模式): 非常类似静默模式只是在每个成功的用例前面有个“ 对勾 ” 每个失败的用例前面有个“F” 2 ( 详细模式):测试结果会显示每个测试用例的所有相关的信息
Test Suite测试套件
完整的单元测试很少只执行一个测试用例,开发人员通常都需要编写多个测试用例才能对某一软件功能进行比较完整的测试,这些相关的测试用例称为一个测试用例集,在unittest中是用TestSuite 类来表示的假设我们已经编写了testbaidu1.py,testbaidu2.py两个文件,那么我们怎么同时执行这两个文件呢?需要使用测试套件
测试套件就是存放测试用例(测试方法)的一个容器,是单元测试用例的结合 其中最常用的类就是TsetSuite类
1.TestSuite()类
该类代表单个测试用例和测试套件的集合。它提供了运行测试所需的接口以使其可以像其他测试一样运行。TestSuite实例和遍历套件相同,单独运行每个测试用例。
TestSuite的行为和TestCase非常相似,但它并未实际执行测试,而是用于将测试用例聚合到一起,下面的2个方法用于向TestSuite实例中添加测试用例:
1.1 addTest():添加测试用例到TestCase或TestSuite套件中; 可以一次存放一个测试脚本里一个类的一个方法(提供方法名)
import unittest
from bite.src.unittest import test001
from bite.src.unittest import test002
def createSuite():
suite = unittest.TestSuite()
suite.addTest(test001.TestCase1("test_baidu1"))
suite.addTest(test001.TestCase1("test_baidu2"))
suite.addTest(test002.TestCase2("test_baidu1"))
suite.addTest(test002.TestCase2("test_baidu2"))
return suite
缺点:一次只能添加一个类里的一个方法,方法太多的时候,比较麻烦
1.2 addTests():将迭代TestCase和TestSuite实例中的所有测试用例添加到此测试组件,相当于调用addTest()然后添加的每个元素。 可以和Testloader结合使用
1.3 makeSuite() :unittest 框架中提供了makeSuite() 的方法。 可以一次性的添加测试脚本的一个类的所有方法(test_ 开头的方法),比起addTest更方便,但是本质上还是在调用addTest方法
def createSuite():
suite = unittest.TestSuite()
suite.addTest(unittest.makeSuite(test001.TestCase1))
suite.addTest(unittest.makeSuite(test002.TestCase2))
return suite
suite.addTest(unittest.makeSuite(test001.TestCase1))
2.TestLoader()类
-
TestLoader类被用来创建类和模块的测试套件。 -
通常不需要创建该类的实例。 unittest框架提供了一个可以共享的实例unittest.defaultTestLoader。 可以一次性存放一个测试脚本的一个类的所有方法,但是用法不太一样,是把类里面的方法先添加到一个小的测试套件 -
一般的情况下,使TestLoader().loadTestsFromTestCase(TestClass) 来加载测试类。
def createSuite():
suite = unittest.TestSuite()
suite1 = unittest.TestLoader().loadTestsFromTestCase(test001.TestCase1)
suite2 = unittest.TestLoader().loadTestsFromTestCase(test002.TestCase2)
suite.addTests([suite1,suite2])
3. discover()方法
可以把指定文件夹下,所有以某种格式命名的测试脚本类中所有以test_开头的方法加入到测试套件中
参数解析 discover(start_dir, pattern =‘test *.py’, top_level_dir = None )
start_dir:要测试的模块名或测试用例目录;
pattern=‘test*.py’:表示用例文件名的匹配原则,下面的例子中匹配文件名为以“test”开头的“.py”文件,星号“*”表示任意多个字符;
top_level_dir=None:测试模块的顶层目录,如果没有顶层目录,默认为None;
该方法通过从指定的开始目录递归到子目录中查找所有测试模块,并返回包含它们的TestSuite对象,只有与模式匹配测试文件和可导入的模块名称才会被加载。
所有测试模块必须可以从项目的顶层导入,如果起始目录不是顶层目录,则顶层目录必须单独指定。
def createSuite():
discover = unittest.defaultTestLoader.discover('../unittest',pattern= 'test00*.py',top_level_dir= None)
return discover
TestRunnner
执行单元测试 在某一个单元测试用鼠标右击只可以启动一个测试用例,如果想同时启动该类的所有测试方法,需要通过python的入口函数。 如果只是单纯的执行测试用例,unittest.main()来启动单元测试的测试模块
if __name__ == "__main__":
unittest.main(verbosity=1)
runner 方法
unittest.TextTestRunnner().run () 执行测试套件
如果是将一个个的测试文件,添加到测试套件之后,就需要运行这个测试用例
if __name__ == "__main__":
suite = createSuite()
runner = unittest.TextTestRunner(verbosity=2)
runner.run(suite)
测试方法/用例执行的顺序
unittest 框架家长测试用例的顺序是根据Ascii码的顺序,数字与字母的顺序 0~9(48- 57) 、 A ~ Z(65- 90) 、 a ~ z(97-122)
忽略测试用例的执行
@unittest.skip(“skipping”) 只需要在需要忽略执行的测试方法前面加上注解即可。
unittest断言
序号 | 断言方法 | 断言描述 |
---|
1 | assertEqual(arg1, arg2, msg=None) | 验证arg1=arg2,不等则fail | 2 | assertNotEqual(arg1, arg2, msg=None) | 验证arg1 != arg2, 相等则fail | 3 | assertTrue(expr, msg=None) | 验证expr是true,如果为false,则fail | 4 | assertFalse(expr,msg=None) | 验证expr是false,如果为true,则fail | 5 | assertIs(arg1, arg2, msg=None) | 验证arg1、arg2是同一个对象,不是则fail | 6 | assertIsNot(arg1, arg2, msg=None) | 验证arg1、arg2不是同一个对象,是则fail | 7 | assertIsNone(expr, msg=None) | 验证expr是None,不是则fail | 8 | assertIsNotNone(expr, msg=None) | 验证expr是None,不是则fail | 9 | assertln (arg1,arg2,msg = None) | 验证arg1 是arg2的字串,不是则fail | 10 | assertlNotn (arg1,arg2,msg = None) | 验证arg1 不是arg2的字串,是则fail | 11 | assertIsInstance(obj,cls,msg = None) | 验证obj是cls的实例,不是则fail | 12 | assertNotIsInstance(obj,cls,msg = None) | 验证obj不是cls的实例,是则fail |
from selenium import webdriver
import time
import unittest
class assert1 (unittest.TestCase):
def setUp(self):
self.driver = webdriver.Chrome()
url = "https://www.baidu.com/"
self.driver.get(url)
def test_asset1(self):
driver = self.driver
driver.find_element_by_id("kw").send_keys("张杰")
driver.find_element_by_id("su").click()
time.sleep(2)
print(driver.title)
self.assertEqual(driver.title,"张杰_百度搜索",msg="not equal!")
self.assertNotEqual(driver.title,"张杰_百度搜索",msg="equal!")
self.assertTrue(1 == 2,msg="False")
def tearDown(self):
time.sleep(2)
self.driver.quit()
if __name__ == '__main__':
unittest.main(verbosity=0)
TestReport
生成测试报告 html报告 有的时候,我们需要执行很多的测试用例,不可能一个个去看是否执行成功,那么久可以使用生成html报告的形式来做。
import unittest
import HTMLTestRunner
import os, sys
import time
def createSuite():
discover = unittest.defaultTestLoader.discover("../unittest", pattern='test00*.py', top_level_dir=None)
return discover
if __name__ == '__main__':
curpath = sys.path[0]
print(curpath)
if not os.path.exists(curpath+"/resultReport"):
os.mkdir(curpath + "/resultReport")
now = time.strftime("%Y-%m-%d %H %M %S",time.localtime(time.time()))
filename = curpath + "/resultReport/" + now + "-" + "resultReport.html"
with open(filename, "wb") as fp:
runner = HTMLTestRunner.HTMLTestRunner(stream=fp, title=u"测试报告",
description=u"测试用例报告", verbosity=2)
suite = createSuite()
runner.run(suite)
suite = createSuite()
run = unittest.TextTestRunner(verbosity= 0)
run.run(suite)
报告界面展示
异常捕获和错误截图
异常捕获的语法和java的类似
def is_alert_exist(self):
try:
self.driver.switch_to.alert
except NoAlertPresentException as e:
return False
return True
如果遇到一些异常的情况,需要把错误信息或者是截图保留下来。
from selenium import webdriver
import unittest
import time
import os,sys
from selenium.common.exceptions import NoAlertPresentException
class TestCase1(unittest.TestCase):
def setUp(self):
self.driver = webdriver.Chrome()
self.url = "https://www.baidu.com/"
self.driver.get(self.url)
self.driver.maximize_window()
time.sleep(2)
def tearDown(self):
self.driver.quit()
def test_baidu1(self):
try:
print(self.driver.title)
self.assertEqual(self.driver.title, "百,你就知道", msg="判断失败,没有打开页面")
except:
self.save_error_image(self.driver, "baidu.png")
def save_error_image(self, driver, name):
if not os.path.exists("./errorImage"):
os.mkdir("./errorImage")
now = time.strftime("%Y-%m-%d #H %M %S", time.localtime(time.time()))
self.driver.get_screenshot_as_file('./errorImage/' + now + "-" + name)
time.sleep(3)
if __name__ == "__main__":
unittest.main(verbosity=1)
由于是不相等的,所以在当前文件的目录下,可以看到一个errorImage 目录下存放的一些错误的截图。
数据驱动
之前我们的case都是数据和代码在一起编写。考虑如下场景: 需要多次执行一个案例,比如baidu搜索,分别输入中文、英文、数字等进行搜索,这样需要分别写多个Test,所以可以使用把要测试的数据写在一个测试文件里面,读取这个数据文件。
python 的unittest 没有自带数据驱动功能。所以如果使用unittest,同时又想使用数据驱动,那么就可以使用DDT 来完成。
ddt.data
装饰测试方法,参数是一系列的值。
同时执行多个测试数据
from selenium import webdriver
import os, time
import unittest
from ddt import ddt, unpack, data, file_data
import sys, csv
@ddt
class ddt(unittest.TestCase):
def setUp(self):
self.driver = webdriver.Chrome()
url = "https://www.baidu.com/"
self.driver.get(url)
@data("xqr", "张杰zj", "阿里巴巴")
def test_baidu(self, value1):
driver = self.driver
driver.find_element_by_id("kw").send_keys(value1)
driver.find_element_by_id("su").click()
time.sleep(2)
def tearDown(self):
time.sleep(3)
self.driver.quit()
if __name__ == '__main__':
unittest.main(verbosity=2)
ddt.file_data
装饰测试方法。参数是文件名。文件可以是json 或者 yaml类型。 注意,如果文件以”.yml”或者”.yaml”结尾,ddt会作为yaml类型处理,其他所有文件都会作为json文件处理。 如果文件中是列表,每个列表的值会作为测试用例参数,同时作为测试用例方法名后缀显示。 如果文件中是字典,字典的key会作为测试用例方法的后缀显示,字典的值会作为测试用例参数。
如果测试的数据很多,那么可以使用读取jason 文件的方式 setUp tearDwon main 都不变化,只需进程如下的写法即可。
@file_data("baidu_test.json")
def test_baidu(self, value1):
driver = self.driver
driver.find_element_by_id("kw").send_keys(value1)
driver.find_element_by_id("su").click()
time.sleep(2)
baidu_test.json
ddt.unpack:
传递的是复杂的数据结构时使用。比如使用元组或者列表,添加unpack之后,ddt会自动把元组或者列表对应到多 个参数上。字典也可以这样处理。
读取list
@data(["谢娜", "谢娜_百度搜索"], ["张杰", "张杰_百度搜索"], ["李宁", "李宁_百度搜索"])
@unpack
def test_baidu1(self, value1, value2):
driver = self.driver
driver.find_element_by_id("kw").send_keys(value1)
driver.find_element_by_id("su").click()
time.sleep(3)
print(driver.title)
self.assertEqual(driver.title, value2, msg="Not Equal")
读取文件
from selenium import webdriver
import os, time
import unittest
from ddt import ddt, unpack, data, file_data
import sys, csv
def getCsv(file_name):
rows = []
path = sys.path[0]
with open(path + "\\" + file_name, 'rt', encoding="UTF-8") as f:
readers = csv.reader(f, delimiter=',', quotechar='|')
next(readers, None)
for row in readers:
temprows = []
for i in row:
temprows.append(i)
rows.append(temprows)
return rows
@ddt
class ddt(unittest.TestCase):
def setUp(self):
self.driver = webdriver.Chrome()
url = "https://www.baidu.com/"
self.driver.get(url)
@data(*getCsv("test_baidu_data.txt"))
@unpack
def test_baidu(self, value1, value2):
driver = self.driver
driver.find_element_by_id("kw").send_keys(value1)
driver.find_element_by_id("su").click()
time.sleep(3)
print(driver.title)
self.assertEqual(driver.title, value2, msg="Not Equal")
def tearDown(self):
time.sleep(3)
self.driver.quit()
if __name__ == '__main__':
unittest.main(verbosity=2)
test_baidu_data.txt (这个data 写不写都行 但是具体原因没查到!) 没什么影响,不太知道为什么。
|