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

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

热门文章

    最新讲堂

      • 推荐阅读
      • 换一换
          •   黑盒测试的原则  依据软件需求文档设计测试用例,验证软件实现满足需求文档的情况,软件文档的正确性对于测试的有效性至关重要。  有针对性地查找问题,并能正确定位问题所在。检查功能实现是否正确,是否存在未实现,未全部实现,实现错误等情况。  根据软件功能的重要性以及时间进度安排确定测试等级以及测试重点,减少程序中出现严重缺陷的可能性。  制定合理的测试计划和测试策略,尽可能发现程序中的错误,并且尽可能的站在用户的角度去进行测试。  分析产品的应用场景、所需支持的设备,尽量模拟生产环境搭建测试环境。  黑盒测试的策略  尽量采用等价类分析法和边界值分析法,这两个方法设计的测试用例对于发现程序的错...
            0 0 675
            分享
          • 读者提问:对于测试架构师日常工作中做哪些事情我还挺好奇的,这个岗位似乎还挺稀缺的,我所经历的公司没有这个岗位。我对测试架构师的理解就是技术架构师,主要是做技术选型,以及带领整个团队做技术提升的。不知道软件测试架构师在实际工作中,都做哪些事情呢?阿常回答:一家业务体系庞大、复杂的公司的测试架构师的职责主要有五个。1、测试团队的技术带头人测试架构师会关注整个团队的技术提升,包括技术难题的攻关,团队遇到的技术难题,你能够做到 90%的解决率。2、深入参与关键业务的评审关注软件的可测试性,作为测试 leader、项目负责人或者测试架构师需要深入参与技术架构的选型,包括前期的概要设计,给出建议。3、测试...
            0 0 1280
            分享
          • 前面的博客中,我介绍了appium多线程中如何获取devices的值。今天我们讲讲如何检查端口是否被占用。因为,我们要获取多线程中的多个端口,但是,在获取多个端口之前,我们得先检查端口是否被占用。思路:通过os.popen命令,接收返回的端口值,如果返回的值的长度大于0,说明端口被占用了,否则,端口就没有被占用。如上,通过创建一个类,写一个方法,检查端口是否被占用。通过对appium多线程自动化的学习,是不是发现,我们每一步动作,都是创建一个类,写一个方法,通过调用这个类中的这个方法,供后面使用。PO模型中,分层设计,后面的类都是调用前面的类中的方法,来达到目的的。多写一写,慢慢就会熟悉pyt...
            0 0 661
            分享
          •   1、引言  在撸码过程中,99.1%的大佬,都不敢说自己的撸出来的代码,是不需要debug的。换句话说,码农在撸码过程中,最痛苦的,莫过于撸出来的代码,为了能避坑,小鱼也是在撸码过程中,总结的一点避坑方法,请各位大佬笑纳。  2、避坑内容总结  2.1无法定位到元素  遇到问题:  找不到元素,脚本报“NoSuchElementException:Unable to find element”,或"定位到了,不能操作,点击无效。  解决方法:  1)查看自己的“属性值”是否写正确  2)元素的标签不唯一,默认找到第一个  3)向上查看,元素是否在frame或iframe框架中  ...
            0 0 1458
            分享
          • 软件测试是对项目研发过程产物(文档、代码、程序等)进行审查,保障产品质量的过程。测试人员应具备从用户角度、开发角度和业务角度审查研发过程产物的能力,从而促使最终的产品达到用户、开发和业务三方要求。一、测试人员的价值是什么自动化测试是当前测试领域的一种重要技术,市面上有jmeter、postman、metersphere等诸多自动化测试工具。越来越多的测试人员将自动化测试作为自身价值的突破点,通过学习掌握更多的自动化测试工具彰显自身价值。那么,测试人员的价值真的就等于自动化测试的水平吗?显而易见,自动化测试是一种新兴的重要的测试技术,是软件测试的一个重要分支。它具有一定的技术门槛和客观的评价指标...
            1 1 1000
            分享
      • 51testing软件测试圈微信