• 13
  • 13
分享

Selenium自动化测试

什么是自动化测试

自动化测试指软件测试的自动化,在预设状态下运行应用程序或者系统,预设条件包括正常和异常,最后评估运行结果。总的概括即:将人为驱动的测试行为转化为机器执行的过程。

进入今天的主角: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的实现原理

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的原理:

  1. 启动web浏览器,后台会同时启动基于Webdriver Wire协议的Web服务器作为selenium的远程服务器,并将其与浏览器绑定。绑定完成之后,服务器就开始监听客户端的操作请求。

  2. 执行测试时,测试用例会作为客户端,将需要执行的页面操作请求以HTTP请求的方式发送给远程服务器。该HTTP请求的正文以Webdriver Wire协议规定的JSON格式来描述需要浏览器执行的具体操作。

  3. 远程服务器接收到请求后,会对请求进行解析,并将解析结果发送给Webdriver,由Webdriver实际执行浏览器的操作。

  4. Webdriver可以看作是直接操作浏览器的原生组件,所以搭建测试环境时,通常需要先下载浏览器对应的WebDriver。

业界有一个形象的比喻来理解:乘客和出租车的例子。乘客就是客户端编写的测试脚本,司机就是我们的WedDriver,而出租车就是浏览器。

1.jpg

Selenium3.0:增加了edge 、Safari的原生驱动

学习Selenium主要就是学习WebDriver常用的API

常见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方法

2.jpg

#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中嵌入的页面跳出,跳回到最外面的原始页面中。

1.jpg

#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")

层级定位

2.jpg

对于这种定位的思路是:先点击显示出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()

下拉框处理

对于下拉框里的内容我们需要两次定位,先定位到下拉框,再定位到下拉框内的选项

1.png

#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()

alert、confirm、prompt的处理

  • text返回alert、confirm、prompt中的文字信息

  • accept点击确认按钮

  • dismiss点击取消按钮

11.png

# -*- 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()

2.png

#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()

div对话框的处理

更多的时候我们在实际应用中碰到的并不是简单的警告框,而是提供更多功能的会话框

1.png

2.jpg

# -*- 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框架解析

unittest是Python的单元测试,它提供了创建测试用例、测试套件以及批量执行的方案。

作为单元测试的框架,unittest也可以对程序最小模块的一种敏捷化的测试。在自动化测试中,我们虽然不需要做白盒测试,但是必须知道所使用语言的单元测试框架。利用单元测试框架,创建一个类,该类继承了unittest的TestCase,这样可以把每个case看成是一个最小的单元,有测试容器组织起来,到时候直接执行,同时引入测试报告。

unittest各组件的关系如图:

3.jpg

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()

addTest()的应用

当有多个或几百个测试用例的时候,就需要一个测试容器(测试套件),把测试用例放在容器中进行执行,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()

makeSuite()和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()的应用

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")

unittest断言

自动化测试中,对于每个单独的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报告生成

执行完脚本之后,还需要看到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)

测试报告用浏览器打开如图所示:

1.jpg

异常捕捉和错误截图

用例不可能每次都运行成功,也有运行不成功的时候,如果可以捕捉错误,这样就能更方便我们定位错误,因此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)

如下就是捕捉到的错误截图:

11.jpg

数据驱动

如果需要多次执行一个案例,比如百度搜索,分别输入中文、英文、数字,或者同类型的多个数据,这时我们就想实现一次运行多个测试用例、unittest没有自带数据驱动功能,所以如果使用unittest,同时又要使用数据驱动,那么就可以使用ddt来完成。

需要安装ddt,可以在控制台输入如下命令:

pip install ddt
python setup.py install

ddt的使用方法

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:<

  • 【留下美好印记】
    赞赏支持
登录 后发表评论
+ 关注

热门文章

    最新讲堂

      • 推荐阅读
      • 换一换
          • 1、引言小屌丝:鱼哥, 都说要想代码写的溜,Lamdba不能少。小鱼:你在项目代码多写几个lamdba试试,看看架构师找不找你喝茶水。小屌丝:…小鱼:逗你玩… 架构师哪舍得自己花钱买茶叶,都是能蹭就蹭…小屌丝:…小鱼:你这突然提到lamdba函数,是想了解点什么??小屌丝:嘿嘿,真是什么都瞒不过你, 我就想把python最常用的内置函数在重新捋一捋,小鱼:说实话。小屌丝:你也知道,我女神最近在学习python,所以我想…小鱼:靠… 就知道你是这样的!小屌丝:鱼哥,我也是身不由己啊, 不然… 说出来都是眼泪。小鱼:行了,别装的这么可怜, 我给你捋一捋不就行了。小屌丝:啥也别说了,此时我想…2、内...
            1 0 1080
            分享
          •   经典软件测试课程免费送,点击下方链接填写测试行业调查问卷,即刻领取,还有机会获得精美测试大礼包~链接:http://vote.51testing.com/   自动化战略  在查看工具的许多选项和构建概念证明之前,第一步是问“为什么”这个问题。  可以说,启动UI自动化项目的最常见原因是“回归测试花费的时间太长”。每个版本,他们有一周的时间开发新功能,然后将新代码交给测试人员,然后有一周的时间对新工作进行测试和错误修复。当一切都完成后,仍然需要有人做一些调查,希望能发现最近所有的代码变动是否引入了任何令人惊讶的问题。最后一部分,回归测试,要么花费太长时间,要么时间紧迫。这就是有人想到所有实...
            0 0 1363
            分享
          •   埃森哲对敏捷测试的定义(与维基百科的定义基本一致)大概如此:敏捷测试是遵从敏捷软件开发原则的一种测试实践。敏捷开发模式把测试集成到了整个开发流程中而不再把它当成一个独立的阶段。因此测试变成了整个软件开发过程中非常重要的环节。敏捷测试包含了具备专业技能测试人员在内的跨职能团队,这使得这种组合式的团队能更好的交付价值,满足项目的业务、质量和进度目标。  从定义中可以看出敏捷测试主要的核心内涵有三个:  1. 是遵从敏捷开发的原则(强调遵守)  2. 测试被包含在整体开发流程中(强调融合)  3. 跨职能团队(强调协作)  除此之外,敏捷测试用到的基本测试方法和技术与传统测试是一样的。  敏捷测...
            12 12 1652
            分享
          •   有一份51Testing测试行业调查问卷需要您的助力,差不多三分钟的时间即可填完。我们给您准备了一份价值398元的测试课程作为礼品,感谢您的帮忙~链接:http://vote.51testing.com/  前言  最近是跳槽季,发现有小伙伴在一些非技术的软性问题上答的不是很好。  众所周知,程序员情商偏低,而这些软性问题,恰恰都具有一定欺骗性和吹牛皮成分在里边,对于演技不好的直男癌,简直就是天生克星。  其实不用太担心,软性问题往往就那几个,稍加训练和准备,你就可以成为一位高端名猿。  题目  第 1 题:说一下你自己的缺点  演技考验:4星  这题处处暗藏杀鸡,很多小伙伴会推心置腹,诚...
            0 0 1268
            分享
          • 沐沐一开始学习python的时候也会觉得枯燥无味,面对各种python的变量、赋值、数字、字符串、列表、元祖等基本知识总是昏昏欲睡。似乎怎么学习都很难将python应用于实际的测试工作。直到我遇到了django,才慢慢的对python有了进一步的实践和认知,所以很有必要安利给大家。比如大家可以用django框架进行简单的博客开发、测试框架开发等、或者可以下载一些开源的测试框架,在本地搭建起来后,熟悉框架的源码,会在框架学习的过程中,慢慢掌握python的各种语法。这里先简单介绍一下,Django是一个开源的Web应用框架,是Python众多框架中的爆款,采用了经典的MVC设计模式。Django...
            1 0 3944
            分享
      • 51testing软件测试圈微信