什么是自动化测试
自动化测试指软件测试的自动化,在预设状态下运行应用程序或者系统,预设条件包括正常和异常,最后评估运行结果。总的概括即:将人为驱动的测试行为转化为机器执行的过程。
进入今天的主角:selenium 学习功能测试自动化首选工具就是selenium,它是一个web自动化测试工具。
selenium的特点
支持多平台:IE、Chrome、Firefox、edge、Safari
支持多语言:Python、C、Java、C#、ruby、js
免费小巧,支持分布式测试用例的执行,可以把测试用例分布到不同的测试机器执行,相当于分发机的功能。
在学习selenium的过程中,我们写测试脚本需要使用unittest框架,这个框架提供的类或函数可以实现我们想要实现的测试功能。自动化测试框架一般可以分为两个层次,上层是管理整个自动化测试的开发,执行以及维护,在比较庞大的项目中,它体现重要的作用,它可以管理整个自动测试,包括自动化测试用例执行的次序、测试脚本的维护、以及集中管理测试用例、测试报告和测试任务等。下层主要是测试脚本的开发,充分的使用相关的测试工具,构建测试驱动,并完成测试业务逻辑。
自动化工具本片博客主要说的是selenium,除此之外,还有QTP、Rational Robot 、jmeter、appium、soapui、Loadrunner等等,以后有时间再去学习一下。工具主要的特点就是每个工具都有自己独特的操作界面供用户使用,selenium中的Selenium IDE就是自动化测试工具。
自动化测试适合适用什么项目,适合在什么时机做这种自动化合适?
自动化测试分为UI自动化测试和接口自动化测试。 UI自动化测试:适用于界面比较稳定的项目,前端开发完成之后,并且项目功能稳定。UI界面测试适合进行回归测试以及兼容性测试。 接口自动化测试:适用于后端开发完成,并且项目功能稳定;后端完成之后,就可以进行接口测试。做接口自动化的工具:soupUI、jmeter。
实施自动化测试的前提条件:需求变动不频繁、项目周期长、自动化测试脚本可以重复使用。
自动化适合的项目:产品型项目、机械并频繁的测试(比如兼容性测试)
降低大型系统的由于变更或者多期开发引起的大量的回归测试的人力投入,这可能是自动化测试最主要的任务,特别是在程序修改比较频繁时,效果是非常明显的,自动化测试前期人力投入较多,但后期进入维护期后,可节省大量人力,而手工测试后期需要增加大量人力用于回归测试。
减少重复测试的时间,实现快速回归测试 创建优良可靠的测试过程,减少人为错误 可以运行更多更繁琐的测试 可以执行一些手工测试困难或不可能进行的测试 更好的利用资源 测试具有一致性和重复性 测试脚本的重用性
Selenium主要有三个版本,分别是Selenium1.0,Selenium2.0,Selenium3.0
Selenium1.0:包括selenium IDE、selenium RC、selenium Grid(支持分布式)。Selenium1.0核心是selenium RC,所以Selenium1.0又称为Selenium RC,它是在浏览器中运用JavaScript应用,即用JavaScript代码获取页面上的任何元素并执行各种操作
Selenium2.0:核心是WebDriver,Selenium+WebDriver
WebDriver的原理:
启动web浏览器,后台会同时启动基于Webdriver Wire协议的Web服务器作为selenium的远程服务器,并将其与浏览器绑定。绑定完成之后,服务器就开始监听客户端的操作请求。
执行测试时,测试用例会作为客户端,将需要执行的页面操作请求以HTTP请求的方式发送给远程服务器。该HTTP请求的正文以Webdriver Wire协议规定的JSON格式来描述需要浏览器执行的具体操作。
远程服务器接收到请求后,会对请求进行解析,并将解析结果发送给Webdriver,由Webdriver实际执行浏览器的操作。
Webdriver可以看作是直接操作浏览器的原生组件,所以搭建测试环境时,通常需要先下载浏览器对应的WebDriver。
业界有一个形象的比喻来理解:乘客和出租车的例子。乘客就是客户端编写的测试脚本,司机就是我们的WedDriver,而出租车就是浏览器。
Selenium3.0:增加了edge 、Safari的原生驱动
学习Selenium主要就是学习WebDriver常用的API
#防止乱码 #coding = utf-8 #想使用selenium的webdriver里面的函数,首先需要把包导进来 from selenium import webdriver import time #我们需要操作的浏览器,这里使用的是谷歌浏览器,也可以是IE、Firefox driver =webdriver.Chrome() #访问百度首页 driver.get('http://www.baidu.com') #停3秒钟 time.sleep(3) #百度输入框的id为kw,我们需要在输入框中输入Selenium,用send_keys进行输入 driver.find_element_by_id("kw").send_keys("Selenium") time.sleep(3) #百度搜索框按钮id叫su,找到后调用click函数模拟点击操作 #和click有相同效果的是submit(),都可以用来点击按钮,submit主要是用于提交表单 driver.find_element_by_id("su").click() time.sleep(3) #退出并关闭窗口的每一个相关的驱动程序 driver.quit()
注意:关闭窗口主要有两种方法,分别是close和quit。close是关闭当前浏览器窗口,quit不仅关闭窗口,还会彻底的退出webdriver,释放driver server之间的连接,所以quit的关闭比close更彻底,它会更好的释放资源。
对页面元素进行定位是自动化测试的核心,我们要想操作一个对象,首先应该识别这个对象。在一个页面中,每个对象属性是唯一的我们需要用一系列的方式去定位我们要操作的对象。WebDriver提供了以下几种方法定位元素:
id
name
class name
link text
partial link text
tag name
xpath
css selector
下面我将对每种定位方法进行举例。
#coding = utf-8 from selenium import webdriver import time driver = webdriver.Chrome() driver.get("https://www.baidu.com") ######################百度输入框的定位方式################# #通过id定位 driver.find_element_by_id("kw").send_keys("selenium") #通过name定位 driver.find_element_by_name("wd").send_keys(u"CSDN博客") #u表示以utf-8的格式输入 #通过tag name定位 driver.find_element_by_tag_name("input").send_keys("Python") #不能成功,因为input在这个页面有多个不唯一,无法定位到底是哪一个 #通过class name定位 driver.find_element_by_class_name("s_ipt").send_keys("Java") #通过CSS定位 driver.find_element_by_css_selector("#kw").send_keys("C++") #通过xpath定位 driver.find_element_by_xpath("//*[@id=kw]").send_keys(u"C语言") #通过link test定位 driver.find_element_by_link_text("hao123").click() #通过partial link test定位 driver.find_element_by_partial_link_text("hao").click() driver.find_element_by_id("su").click() time.sleep(3) driver.quit()
前面说过等待可以引入time包,从而在脚本中自由的添加休眠时间 。但是有时候我们不想等待一个固定的时间,于是可以通过implicitly_wait()方法方便的实现智能等待,它在一个时间范围内智能等待。
selenium.webdriver.remote.webdriver.implicitly_wait(time_to_wait)隐式等待一个元素被发现或一个命令完成,这个方法每次会话只需要调用一次time_to_wait
具体用法:
# coding = utf-8 from selenium import webdriver import time #调入time 函数 browser = webdriver.Chrome() browser.get("http://www.baidu.com") browser.implicitly_wait(30) #智能等待30秒 browser.find_element_by_id("kw").send_keys("selenium") browser.find_element_by_id("su").click() browser.quit()
使用print打印title和URL
#coding = utf-8 from selenium import webdriver driver = webdriver.Chrome() driver.get('http://www.baidu.com') print driver.title # 把页面title 打印出来 print driver.current_url #打印url driver.quit()
浏览器的最大化
调用启动的浏览器不是全屏的,这样虽然不影响脚本的执行,但有时会影响我们观看脚本执行的变化。使用maximize_window()
#coding=utf-8 from selenium import webdriver import time browser = webdriver.Chrome() browser.get("http://www.baidu.com") print "浏览器最大化" browser.maximize_window() #将浏览器最大化显示 time.sleep(2) browser.find_element_by_id("kw").send_keys("selenium") browser.find_element_by_id("su").click() time.sleep(3) browser.quit()
最大化不够灵活,所以我们可以随意的设置浏览页面的宽高,使用set_window_size(宽,高)
#coding=utf-8 from selenium import webdriver import time browser = webdriver.Chrome() browser.get("http://www.baidu.com") time.sleep(2) #参数数字为像素点 print "设置浏览器宽480、高800显示" browser.set_window_size(480, 800) time.sleep(3) browser.quit()
我们也可以实现浏览器的前进和后退
#coding=utf-8 from selenium import webdriver import time browser = webdriver.Chrome() #访问百度首页 first_url= 'http://www.baidu.com' print "now access %s" %(first_url) browser.get(first_url) time.sleep(2) #访问新闻页面 second_url='http://news.baidu.com' print "now access %s" %(second_url) browser.get(second_url) time.sleep(2) #返回(后退)到百度首页 print "back to %s "%(first_url) browser.back() time.sleep(1) #前进到新闻页 print "forward to %s"%(second_url) browser.forward() time.sleep(2) browser.quit()
#coding=utf-8 from selenium import webdriver import time #访问百度 driver=webdriver.Chrome() driver.get("http://www.baidu.com") #搜索 driver.find_element_by_id("kw").send_keys("selenium") driver.find_element_by_id("su").click() time.sleep(3) #将页面滚动条拖到底部 js="var q=document.documentElement.scrollTop=10000" driver.execute_script(js) time.sleep(3) #将滚动条移动到页面的顶部 js="var q=document.documentElement.scrollTop=0" driver.execute_script(js) time.sleep(3) driver.quit() #excute_script(script,*args),在当前窗口同步执行JavaScript
键盘键用法
#coding=utf-8 from selenium import webdriver from selenium.webdriver.common.keys import Keys #需要引入keys 包 import os,time driver = webdriver.Chrome() driver.get("http://demo.zentao.net/user-login-Lw==.html") time.sleep(3) driver.maximize_window() # 浏览器全屏显示 driver.find_element_by_id("account").clear() time.sleep(3) driver.find_element_by_id("account").send_keys("demo") time.sleep(3) #tab 的定位相当于清除了密码框的默认提示信息,等同上面的clear() driver.find_element_by_id("account").send_keys(Keys.TAB) time.sleep(3) #通过定位密码框,enter(回车)来代替登陆按钮 driver.find_element_by_name("password").send_keys(Keys.ENTER) time.sleep(3) driver.quit()
实现Ctrl+a,Ctrl+x
#coding=utf-8 from selenium import webdriver from selenium.webdriver.common.keys import Keys import time driver = webdriver.Chrome() driver.get("http://www.baidu.com") #输入框输入内容 driver.find_element_by_id("kw").send_keys("selenium") time.sleep(3) #ctrl+a 全选输入框内容 driver.find_element_by_id("kw").send_keys(Keys.CONTROL,'a') time.sleep(3) #ctrl+x 剪切输入框内容 driver.find_element_by_id("kw").send_keys(Keys.CONTROL,'x') time.sleep(3) #输入框重新输入内容,搜索 driver.find_element_by_id("kw").send_keys("webdriver") driver.find_element_by_id("su").click() time.sleep(3) driver.quit()
操作鼠标需要使用到ActionChains类
context_click()右击
double_click()双击
drag_and_drop()拖动
move_to_element()移动
#coding=utf-8 from selenium import webdriver from selenium.webdriver.common.keys import Keys from selenium.webdriver.common.action_chains import ActionChains import time driver = webdriver.Chrome() driver.get("http://news.baidu.com") qqq =driver.find_element_by_xpath(".//*[@id='s_btn_wr']") ActionChains(driver).context_click(qqq).perform() #右键 ActionChains(driver).double_click(qqq).perform() #双击 #定位元素的原位置 element = driver.find_element_by_id("s_btn_wr") #定位元素要移动到的目标位置 target = driver.find_element_by_class_name("btn") #执行元素的移动操作 ActionChains(driver).drag_and_drop(element, target).perform() #ActionChains(driver)生成用户的行为。所有的行动都存储在actionchains 对象。通过perform()存储的行为。 #move_to_element(menu)移动鼠标到一个元素中,menu 上面已经定义了他所指向的哪一个元素 #perform()执行所有存储的行为
前面我们定位元素时,只定位了某一个特定的对象,但有时我们需要定位一组对象,这就需要使用find_elements方法
#coding=utf-8 from selenium import webdriver import time import os dr = webdriver.Chrome() file_path = 'file:///' + os.path.abspath('D:\\Users\\320S-15\\seleniumTestHTML\\checkbox.html') dr.get(file_path) # 选择页面上所有的input,然后从中过滤出所有的checkbox 并勾选之 inputs = dr.find_elements_by_tag_name('input') for input in inputs: if input.get_attribute('type') == 'checkbox': input.click() time.sleep(2) dr.quit() #get_attribute获取属性值
switch_to_frame()多层框架定位
switch_to_window() 多窗口定位
switch_to_frame()的功能是把当前定位的主体切换到frame里,即frame中实际上嵌入了另一个页面,而webdriver每次只能在一个页面识别,因此才需要用switch_to_frame方法去获取frame中嵌入的页面,对那个页面里的元素进行定位。
switch_to _default_content:从frame中嵌入的页面跳出,跳回到最外面的原始页面中。
#coding=utf-8 from selenium import webdriver import time import os browser = webdriver.Chrome() file_path = 'file:///' + os.path.abspath('D:\\Users\\320S-15\\seleniumTestHTML\\frame.html') browser.get(file_path) browser.implicitly_wait(30) #先找到到ifrome1(id = f1) browser.switch_to_frame("f1") #再找到其下面的ifrome2(id =f2) browser.switch_to_frame("f2") #下面就可以正常的操作元素了 browser.find_element_by_id("kw").send_keys("selenium") browser.find_element_by_id("su").click() time.sleep(3) browser.quit()
多层窗口定位:switch_to_window用法与switch_to_frame相同,如:driver.switch_to_window("windowname")
对于这种定位的思路是:先点击显示出1个下拉菜单,然后再定位该下拉菜单所在的ul,再定位这个ul下的某个具体的link,如果要定位第一个下拉菜单中的Action选项:
#coding=utf-8 from selenium import webdriver from selenium.webdriver.support.ui import WebDriverWait import time import os dr = webdriver.Chrome() file_path = 'file:///' + os.path.abspath('D:\\Users\\320S-15\\seleniumTestHTML\\level_locate.html') dr.get(file_path) #点击Link1链接(弹出下拉列表) dr.find_element_by_link_text('Link1').click() #找到id为dropdown1的父元素 WebDriverWait(dr,10).until(lambda the_driver:#10秒内每隔500毫秒扫描1次页面变化,当出现指定的元素结束 the_driver.find_element_by_id('dropdown1').is_displayed())#is_displayed()表示是否用户可见 #在父亲元件下找到link 为Action 的子元素 menu = dr.find_element_by_id('dropdown1').find_element_by_link_text('Action') #鼠标定位到子元素上 webdriver.ActionChains(dr).move_to_element(menu).perform() time.sleep(2) dr.quit()
对于下拉框里的内容我们需要两次定位,先定位到下拉框,再定位到下拉框内的选项
#coding=utf-8 from selenium import webdriver import os,time driver= webdriver.Chrome() file_path = 'file:///' + os.path.abspath('D:\\Users\\320S-15\\seleniumTestHTML\\drop_down.html') driver.get(file_path) time.sleep(2) #先定位到下拉框 m=driver.find_element_by_id("ShippingMethod") #再点击下拉框下的选项 m.find_element_by_xpath("//option[@value='10.69']").click() time.sleep(3) driver.quit()
text返回alert、confirm、prompt中的文字信息
accept点击确认按钮
dismiss点击取消按钮
# -*- coding: utf-8 -*- from selenium import webdriver from time import sleep import os dr = webdriver.Chrome() file_path = 'file:///' + os.path.abspath('D:\\Users\\320S-15\\seleniumTestHTML\\alert.html') dr.get(file_path) # 点击链接弹出alert dr.find_element_by_id('tooltip').click() sleep(2) alert = dr.switch_to.alert alert.accept() sleep(2) dr.quit()
#coding:utf-8 from selenium import webdriver from time import sleep import os driver=webdriver.Chrome() driver.implicitly_wait(30) file_path = 'file:///' + os.path.abspath('D:/Users/320S-15/seleniumTestHTML/send.html') driver.get(file_path) #点击“请点击” driver.find_element_by_xpath("html/body/input").click() #输入内容 driver.switch_to.alert.send_keys('webdriver') driver.switch_to.alert.accept() sleep(5) driver.quit()
更多的时候我们在实际应用中碰到的并不是简单的警告框,而是提供更多功能的会话框
# -*- coding: utf-8 -*- from selenium import webdriver from time import sleep import os import selenium.webdriver.support.ui as ui dr = webdriver.Chrome() file_path = 'file:///' + os.path.abspath('D:/Users/320S-15/seleniumTestHTML/modal.html') dr.get(file_path) # 打开对话框 dr.find_element_by_id('show_modal').click() sleep(3) # 点击对话框中的链接 link = dr.find_element_by_id('myModal').find_element_by_id('click') link.click() #dr.execute_script('$(arguments[0]).click()', link) sleep(4) # 关闭对话框 buttons =dr.find_element_by_class_name('modal-footer').find_elements_by_tag_name('button') buttons[0].click() sleep(2) dr.quit()
上传文件过程一般要打开一个本地窗口,从窗口选择本地文件添加。所以selenium webdriver实现的方法是:只要定位上传按钮,通过send_keys添加本地文件路径就可以了,绝对路径和相对路径都可以,关键是上传的文件存在。
#coding=utf-8 from selenium import webdriver import os,time driver = webdriver.Chrome() #脚本要与upload_file.html 同一目录 file_path = 'file:///' + os.path.abspath('D:/Users/320S-15/seleniumTestHTML/upload.html') driver.get(file_path) #定位上传按钮,添加本地文件 driver.find_element_by_name("file").send_keys('E:\\320S-15\\test\\baidu.py') time.sleep(2) driver.quit()
unittest是Python的单元测试,它提供了创建测试用例、测试套件以及批量执行的方案。
作为单元测试的框架,unittest也可以对程序最小模块的一种敏捷化的测试。在自动化测试中,我们虽然不需要做白盒测试,但是必须知道所使用语言的单元测试框架。利用单元测试框架,创建一个类,该类继承了unittest的TestCase,这样可以把每个case看成是一个最小的单元,有测试容器组织起来,到时候直接执行,同时引入测试报告。
unittest各组件的关系如图:
Test Fixture:测试固件,初始化和清理测试环境,比如创建临时的数据库、文件和目录等,其中setUp和tearDown是最常用的 TestCase:单元测试用例,是编写单元测试用例常用的类 TestSuite:单元测试用例的集合,TestSuite也是常用的类 TestRunner:执行单元测试 TestReport:生成测试报告
这里我们简单的创建一个测试用例:
# -*- coding: utf-8 -*- from selenium import webdriver from selenium.webdriver.common.by import By from selenium.webdriver.common.keys import Keys from selenium.webdriver.support.ui import Select from selenium.common.exceptions import NoSuchElementException from selenium.common.exceptions import NoAlertPresentException import unittest, time class Baidu1(unittest.TestCase): def setUp(self): print(u"setUp方法") self.driver = webdriver.Chrome() self.driver.implicitly_wait(30) self.base_url = "http://www.baidu.com/" self.verificationErrors = [] self.accept_next_alert = True #test fixture,清除环境 def tearDown(self): print(u"tearDown方法") self.driver.quit() self.assertEqual([],self.verificationErrors) def test_hao(self): driver = self.driver driver.get(self.base_url + "/") driver.find_element_by_link_text("hao123").click() time.sleep(6) self.assertEqual(u"hao123_上网从这里开始", driver.title) # @unittest.skip("skipping") def test_baidusearch(self): driver = self.driver driver.get(self.base_url + "/") driver.find_element_by_id("kw").click() driver.find_element_by_id("kw").clear() driver.find_element_by_id("kw").send_keys(u"雪之城") driver.find_element_by_id("su").click() #判断element是否存在,可删除 def is_element_present(self, how, what): try: self.driver.find_element(by=how, value=what) except NoSuchElementException as e: return False return True #关闭alert,可删除 def is_alert_present(self): try: self.driver.switch_to.alert except NoAlertPresentException as e: return False return True def close_alert_and_get_its_text(self): try: alert = self.driver.switch_to.alert alert_text = alert.text if self.accept_next_alert: alert.accept() else: alert.dismiss() return alert_text finally: self.accept_next_alert = True if __name__ == "__main__": unittest.main(verbosity=2)
可以增加verbosity参数,例如unittest.main(verbosity=2)。在主函数中,直接调用main() ,在main中加入verbosity=2 ,这样测试的结果就会显示的更加详细。这里的verbosity 是一个选项, 表示测试结果的信息复杂度,有三个值:
0 ( 静默模式): 你只能获得总的测试用例数和总的结果比如总共100个,失败20 成功80。
1 ( 默认模式): 非常类似静默模式只是在每个成功的用例前面有个“ . ” 每个失败的用例前面有个“F”。
2 ( 详细模式): 测试结果会显示每个测试用例的所有相关的信息。
构建测试套件
完整的单元测试需要执行很多个测试用例,开发人员通常需要编写多个测试用例才能对某一软件的功能进行比较完整的测试,这些相关的测试用例称为一个测试用例集,在unittest中是用TestSuite类表示的。
如果我们编写了testbaidu1.py和testbaidu2.py两个文件,那我们怎么同时执行这两个文件呢?
# encoding: utf-8 from selenium import webdriver from selenium.webdriver.common.by import By from selenium.webdriver.common.keys import Keys from selenium.webdriver.support.ui import Select from selenium.common.exceptions import NoSuchElementException from selenium.common.exceptions import NoAlertPresentException import unittest, time, re class Baidu2(unittest.TestCase): #test fixture,初始化环境 def setUp(self): print(u"setUp方法") self.driver = webdriver.Chrome() self.driver.implicitly_wait(30) self.base_url = "http://www.baidu.com/" self.verificationErrors = [] self.accept_next_alert = True #test fixture,清除环境 def tearDown(self): print(u"tearDown方法") self.driver.quit() self.assertEqual([], self.verificationErrors) #测试用例,必须以test开头 def test_baidusearch(self): driver = self.driver driver.get(self.base_url + "/") driver.find_element_by_id("kw").click() driver.find_element_by_id("kw").clear() driver.find_element_by_id("kw").send_keys(u"selenium") driver.find_element_by_id("su").click() driver.find_element_by_id("su").click() #判断element是否存在,可删除 def is_element_present(self, how, what): try: self.driver.find_element(by=how, value=what) except NoSuchElementException as e: return False return True #判断alert是否存在,可删除 def is_alert_present(self): try: self.driver.switch_to_alert() except NoAlertPresentException as e: return False return True #关闭alert,可删除 def close_alert_and_get_its_text(self): try: alert = self.driver.switch_to.alert alert_text = alert.text if self.accept_next_alert: alert.accept() else: alert.dismiss() return alert_text finally: self.accept_next_alert = True if __name__ == "__main__": #执行用例 unittest.main(verbosity=2)
# -*- coding: utf-8 -*- from selenium import webdriver from selenium.webdriver.common.by import By from selenium.webdriver.common.keys import Keys from selenium.webdriver.support.ui import Select from selenium.common.exceptions import NoSuchElementException from selenium.common.exceptions import NoAlertPresentException import unittest, time class Baidu1(unittest.TestCase): def setUp(self): print(u"setUp方法") self.driver = webdriver.Chrome() self.driver.implicitly_wait(30) self.base_url = "http://www.baidu.com/" self.verificationErrors = [] self.accept_next_alert = True #test fixture,清除环境 def tearDown(self): print(u"tearDown方法") self.driver.quit() self.assertEqual([],self.verificationErrors) def test_hao(self): driver = self.driver driver.get(self.base_url + "/") driver.find_element_by_link_text("hao123").click() time.sleep(6) self.assertEqual(u"hao123_上网从这里开始", driver.title) # @unittest.skip("skipping") def test_baidusearch(self): driver = self.driver driver.get(self.base_url + "/") driver.find_element_by_id("kw").click() driver.find_element_by_id("kw").clear() driver.find_element_by_id("kw").send_keys(u"雪之城") driver.find_element_by_id("su").click() #判断element是否存在,可删除 def is_element_present(self, how, what): try: self.driver.find_element(by=how, value=what) except NoSuchElementException as e: return False return True #关闭alert,可删除 def is_alert_present(self): try: self.driver.switch_to.alert except NoAlertPresentException as e: return False return True def close_alert_and_get_its_text(self): try: alert = self.driver.switch_to.alert alert_text = alert.text if self.accept_next_alert: alert.accept() else: alert.dismiss() return alert_text finally: self.accept_next_alert = True if __name__ == "__main__": unittest.main(verbosity=2)
这里就需要构建测试套件,构建测试套件有三种方法,不同的情景下用不同的方法:
直接加入测试方法:addTest()
makeSuit()/TestLoader()
discover()
当有多个或几百个测试用例的时候,就需要一个测试容器(测试套件),把测试用例放在容器中进行执行,unittest模块中提供了addSuite类来生成测试套件,使用该类的构造函数可以生成一个测试套件的实例,该类提供了addTest把每个测试用例加入到测试套件中。
将testbaidu1.py,testbaidu2.py,runall.py放在同一级目录下,runall.py文件如下:
# -*- coding: utf-8 -*- import unittest,csv import os,sys import time #导入testbaidu1,testbaidu2 import testbaidu1 import testbaidu2 #手工添加案例到套件, def createsuite(): suite = unittest.TestSuite() #将测试用例加入到测试容器(套件)中 suite.addTest(testbaidu1.Baidu1("test_baidusearch")) suite.addTest(testbaidu1.Baidu1("test_hao")) suite.addTest(testbaidu2.Baidu2("test_baidusearch")) return suite if __name__=="__main__": suite = createsuite() runner = unittest.TextTestRunner(verbosity=2) runner.run(suite)
上述做法有两个不方便的地方,阻碍脚本的快速执行,必须每次修改runall.py:
需要导入所有的py文件,比如import testbaidu1,每新增一个需要导入一个
addTest需要增加所有的testcase,如果一个py文件中有10个case,就需要增加10次
于是就引入了makeSuit()/TestLoader()
在unittest 框架中提供了makeSuite() 的方法,makeSuite可以实现把测试用例类内所有的测试case组成的测试套件TestSuite ,unittest 调用makeSuite的时候,只需要把测试类名称传入即可。TestLoader 用于创建类和模块的测试套件,一般的情况下TestLoader().loadTestsFromTestCase(TestClass)来加载测试类。
修改runall.py
# -*- coding: utf-8 -*- import unittest import time import testbaidu1 import testbaidu2 #手工添加案例到套件, def createsuite(): #suite = unittest.TestSuite() #将测试用例加入到测试容器(套件)中 ######### makeSuite() ############### # suite.addTest(unittest.makeSuite(testbaidu1.Baidu1)) # suite.addTest(unittest.makeSuite(testbaidu2.Baidu2)) # return suite ########## TestLoader() ############## suite1 = unittest.TestLoader().loadTestsFromTestCase(testbaidu1.Baidu1) suite2 = unittest.TestLoader().loadTestsFromTestCase(testbaidu2.Baidu2) suite = unittest.TestSuite([suite1, suite2]) return suite if __name__=="__main__": suite = createsuite() runner = unittest.TextTestRunner(verbosity=2) runner.run(suite)
经过makeSuite()和TestLoader()的引入,我们不用一个py文件测试类,只需要导入一次即可。
那么能不能测试类也不用每次添加指定呢?
discover 是通过递归的方式到其子目录中从指定的目录开始, 找到所有测试模块并返回一个包含它们对象的TestSuite ,然后进行加载与模式匹配唯一的测试文件,discover 参数分别为discover(dir,pattern,top_level_dir=None)
__author__ = '320S-15' # -*- coding: utf-8 -*- import unittest,csv import os,sys import time #手工添加案例到套件, def createsuite(): discover=unittest.defaultTestLoader.discover('../test',pattern='testbaidu*.py',top_level_dir=None) print discover return discover if __name__=="__main__": suite=createsuite() runner = unittest.TextTestRunner(verbosity = 2) runner.run(suite)
unittest 框架默认加载测试用例的顺序是根据ASCII 码的顺序,数字与字母的顺序为: 0~9,A~Z,a~z 。所以, TestAdd 类会优先于TestBdd 类被发现, test_aaa() 方法会优先于test_ccc() 被执行。对于测试目录与测试文件来说, unittest 框架同样是按照这个规则来加载测试例。
如果想忽略某个用例的执行,可在方法前加@unittest.skip("skipping")
自动化测试中,对于每个单独的case来说,一个case执行结果中,必然会有期望结果与实际结果,来判断该case是通过还是失败。在unittest的库中提供了大量实用方法来检查预期值与实际值来验证case的结果。一般来说,检查条件大体分为等价性、逻辑比较以及其他,如果给定的断言通过,测试会继续执行到下一行的代码,如果断言失败,对应的case测试会立即停止或者生成错误信息(一般可以打印错误信息即可),但不要影响其他的case执行。
unittest的单元测试提供了标准的XUnit断言方法。下面是一些常见的断言:
断言方法 | 断言描述 |
assertEqual(arg1,arg2,msg=None) | 验证arg1 = arg2,不等则fail |
assertNotEqual(arg1,arg2,msg=None) | 验证arg1 != arg2,不等则fail |
assertTrue(exxpr,msg=None) | 验证expr是true,如果false,则fail |
assertFalse(exxpr,msg=None) | 验证expr是false,如果true,则fail |
assertIs(arg1, arg2, msg=None) | 验证arg1、arg2是同一个对象,不是则fail |
assertIsNot(arg1, arg2, msg=None) | 验证arg1、arg2不是同一个对象,是则fail |
assertIsNone(expr, msg=None) | 验证expr是None,不是则fail |
assertIsNotNone(expr, msg=None) | 验证expr不是None,是则fail |
assertIn(arg1, arg2, msg=None) | 验证arg1是arg2的子串,不是则fail |
assertNotIn(arg1, arg2, msg=None) | 验证arg1不是arg2的子串,是则fail |
assertIsInstance(obj, cls, msg=None) | 验证obj是cls的实例,不是则fail |
assertNotIsInstance(obj, cls, msg=None) | 验证obj不是cls的实例,是则fail |
如前面的例子中:self.assertEqual(u"hao123_上网从这里开始", driver.title)
执行完脚本之后,还需要看到HTML报告,可以通过HTMLTestRunner.py生成测试报告。因为我本机安装的是Python2.7,所以需要下载HTMLTestRunner.py文件,下载地址:http://tungwaiyip.info/software/HTMLTestRunner.html。下载后将其放在testcase目录下或放入安装路径...\Python2.7\Lib目录下。
将上面的runall.py改成:
# -*- coding: utf-8 -*- import unittest,csv import os,sys import time import HTMLTestRunner #手工添加案例到套件, def createsuite(): discover=unittest.defaultTestLoader.discover('../test',pattern='testbaidu*.py',top_level_dir=None) print discover return discover if __name__=="__main__": curpath=sys.path[0] print(sys.path) print("=================") print(sys.path[0]) #取当前时间 now=time.strftime("%Y-%m-%d-%H %M %S",time.localtime(time.time())) if not os.path.exists(curpath+'/resultreport'): os.makedirs(curpath+'/resultreport') print("=================") print(time.time()) print("=================") print(time.localtime(time.time())) print("=================") print(now) #在当前目录下创建一个子目录resultreport,然后在该子目录下生成一个resultreport.html文件 filename=curpath+'/resultreport/'+now+'resultreport.html' with open(filename,'wb') as fp: #生成html报告 runner=HTMLTestRunner.HTMLTestRunner(stream=fp,title=u'测试报告',description=u'用例执行情况',verbosity=2) suite=createsuite() runner.run(suite)
测试报告用浏览器打开如图所示:
用例不可能每次都运行成功,也有运行不成功的时候,如果可以捕捉错误,这样就能更方便我们定位错误,因此unittest中有一个函数get_screenshot_as_file可以实现错误截图功能。
脚本实现实例:
# -*- coding: utf-8 -*- from selenium import webdriver from selenium.webdriver.common.by import By from selenium.webdriver.common.keys import Keys from selenium.webdriver.support.ui import Select from selenium.common.exceptions import NoSuchElementException from selenium.common.exceptions import NoAlertPresentException import unittest, time, re import os class Baidu1(unittest.TestCase): #test fixture,初始化环境 def setUp(self): self.driver = webdriver.Chrome() self.driver.implicitly_wait(30) self.base_url = "http://www.baidu.com/" self.verificationErrors = [] self.accept_next_alert = True #测试用例,必须以test开头 def test_hao(self): driver = self.driver driver.get(self.base_url) driver.find_element_by_link_text("hao123").click() time.sleep(2) try: self.assertEqual(u'hao123_上网从这里开始', driver.title) except: self.savescreenshot(driver,'hao.png') #判断element是否存在,可删除 def is_element_present(self, how, what): try: self.driver.find_element(by=how, value=what) except NoSuchElementException as e: return False return True #判断alert是否存在,可删除 def is_alert_present(self): try: self.driver.switch_to.alert except NoAlertPresentException as e: return False return True #关闭alert,可删除 def close_alert_and_get_its_text(self): try: alert = self.driver.switch_to.lert alert_text = alert.text if self.accept_next_alert: alert.accept() else: alert.dismiss() return alert_text finally: self.accept_next_alert = True #test fixture,清除环境 def tearDown(self): self.driver.quit() self.assertEqual([], self.verificationErrors) #截图 def savescreenshot(self,driver,file_name): if not os.path.exists('./image'):#如果不存在image文件夹,我们就创建之 os.makedirs('./image') now=time.strftime("%Y%m%d-%H%M%S",time.localtime(time.time())) print(time.time()) print(time.localtime(time.time())) print(now) #截图保存 driver.get_screenshot_as_file('./image/'+now+'-'+file_name) time.sleep(1) if __name__ == "__main__": #执行用例 unittest.main(verbosity=2)
如下就是捕捉到的错误截图:
如果需要多次执行一个案例,比如百度搜索,分别输入中文、英文、数字,或者同类型的多个数据,这时我们就想实现一次运行多个测试用例、unittest没有自带数据驱动功能,所以如果使用unittest,同时又要使用数据驱动,那么就可以使用ddt来完成。
需要安装ddt,可以在控制台输入如下命令:
pip install ddt python setup.py install
dd.ddt
装饰类,也就是继承自TestCase的类。
ddt.data:
装饰测试方法。参数是一系列的值。
ddt.file_data:
装饰测试方法。参数是文件名。文件可以是json 或者 yaml类型。
注意,如果文件以”.yml”或者”.yaml”结尾,ddt会作为yaml类型处理,其他所有文件都会作为json文件处理。
如果文件中是列表,每个列表的值会作为测试用例参数,同时作为测试用例方法名后缀显示。
如果文件中是字典,字典的key会作为测试用例方法的后缀显示,字典的值会作为测试用例参数。
ddt.unpack:
传递的是复杂的数据结构时使用。比如使用元组或者列表,添加unpack之后,ddt会自动把元组或者列表对应到多个参数上。字典也可以这样处理。
实现一个Testddt类,测试ddt.data
#-*- coding: utf-8 -*- from selenium import webdriver from selenium.webdriver.common.by import By from selenium.webdriver.common.keys import Keys from selenium.webdriver.support.ui import Select from selenium.common.exceptions import NoSuchElementException from selenium.common.exceptions import NoAlertPresentException import unittest, time, re import os,sys,csv from ddt import ddt, data, unpack ,file_data def getCsv(file_name): rows=[] path=sys.path[0].replace('\test','') print(path) with open(path+'/data/'+file_name,'rb') as f: readers=csv.reader(f,delimiter=',',quotechar='|') next(readers,None) for row in readers: temprows=[] for i in row: temprows.append(i.decode('gbk')) rows.append(temprows) return rows #引入ddt @ddt class Testddt(unittest.TestCase): def setUp(self): self.driver = webdriver.Chrome() self.driver.implicitly_wait(30) self.base_url = "http://www.baidu.com" self.verificationErrors = [] self.accept_next_alert = True @data(*getCsv('test_baidu_data.csv')) @unpack def test_hao(self,value,expected_value): driver = self.driver driver.get(self.base_url + "/") driver.find_element_by_id("kw").clear() driver.find_element_by_id("kw").send_keys(value) driver.find_element_by_id("su").click() time.sleep(2) print(value) self.assertEqual(expected_value, driver.title) print expected_value print driver.title #判断element是否存在,可删除 def is_element_present(self, how, what): try: self.driver.find_element(by=how, value=what) except NoSuchElementException as e: return False return True #判断alert是否存在,可删除 def is_alert_present(self): try: self.driver.switch_to.alert except NoAlertPresentException as e:return False return True #关闭alert,可删除 def close_alert_and_get_its_text(self): try: alert = self.driver.switch_to.alert alert_text = alert.text if self.accept_next_alert: alert.accept() else: alert.dismiss() return alert_text finally: self.accept_next_alert = True #test fixture,清除环境 def tearDown(self): self.driver.quit() self.assertEqual([], self.verificationErrors) def savescreenshot(self,driver,file_name): if not os.path.exists('./image'): os.makedirs('./image') now=time.strftime("%Y%m%d-%H%M%S",time.localtime(time.time())) #截图保存 driver.get_screenshot_as_file('./image/'+now+'-'+file_name) time.sleep(1) if __name__ == "__main__": #执行用例 unittest.main(verbosity=2)
test_baidu_data.csv:<