1. 什么是框架
1.1 定义:
框架(Framework)是整个或部分系统的可重用设计,表现为一组抽象构件(类)及构件(类)实例间交互的方法。
1.2 为什么要搭建自动化测试框架
自动化测试的开发,通常是由自动化测试的需求决定的。这个需求主要包括:
-
- 自动化测试更便于实施
- 解决自动化测试脚本本身存在的问题,如异常处理和场景恢复。
- 测试易于维护。好的框架,可以减少你在管理维护中所投入的人力物力精力。
- 可重用性。框架的意义之一就在于可重用吧。所以在框架里,你可以实现一些通用功能,简化脚本开发过程。
- 美观易读的测试报告。拿Selenium来说,它产出的测试报告只是基于测试脚本的,并没有那种基于测试集的报告,所以如果你要,测试框架里可以实现。
1.3 自动化测试框架
(1)定义:是一个集成体系,在这一体系中包含测试功能的函数库、测试数据源、测试对象识别标准,以及种可重用的模块。
(2)经历阶段: 自动化测试框架在发展的过程中经历了几个阶段,模块驱动测试、数据驱动测试、对象驱动测试。
2. 什么是设计模式?
2.1 定义
设计模式是软件开发人员在软件开发过程中面临的一般问题的解决方案。这些解决方案是众多软件开发人员经过相当长的一段时间的试验和错误总结出来的。
2.2使用设计模式
是为了重用代码、让代码更容易被他人理解、保证代码可靠性。项目中合理地运用设计模式可以完美地解决很多问题,每种模式在现实中都有相应的原理来与之对应,每种模式都描述了一个在我们周围不断重复发生的问题,以及该问题的核心解决方案,这也是设计模式能被广泛应用的原因。
参见网站:http://www.runoob.com/design-pattern/design-pattern-tutorial.html
3. Page Object Model设计模式
3.1 定义
对于简单的Selenium自动化测试,我们要做的不过是找到页面元素,并且值传递给这些元素。但是假如有10个脚本同时调用了一个相同的页面元素,当这个元素发生改变,我们需要修改10个脚本。随着脚本数的增加,时间工作复杂度也飞速增长。这个时候我们就可以考虑设计一个类,专门用来页面元素的查找、传递值和修正。这样,当一个页面元素发生改变的时候,只用修改一个类,而不用同时修改10个脚本。
Page Object是一种程序设计模式,将面向过程转变为面向对象(页面对象),将测试对象(按钮、输入框、标题等)及单个的测试步骤封装在每个Page对象中,以page为单位进行管理。
这样,在Selenium测试页面中可以通过调用页面类来获取页面元素,从而巧妙的避免了当页面元素id或者位置变化时,需要改测试页面代码的情况。当页面元素id变化时,只需要更改测试页Class中页面的属性即可。可以使代码复用,降低维护成本,提高程序可读性和编写效率。
3.2 优点
- 以页面为单位,集中管理元素对象和方法。当页面元素或流程变动时只需要修改相关页面方法即可,不需要修改相应脚本
- 编写脚本简单,顺着业务逻辑写脚本。page object模式以业务逻辑上的每一步操作作为区分点,页面方法代表了此页面的一个业务操作并严格控制此操作的后续流程
- 后期维护方便
3.3 Page Factory
Page Factory是一个很好的设计Object repository的模式,以此来实现Selenium WebDriver中的POM概念。
4. Page Object的对象
4.1 Page模式:
1. 抽象出一个BasePage基类,包含一个指向selenium.webdriver的属性
2. 每一个webpage都继承BasePage,通过driver来获取本页面的元素,每个页面的操作都抽象为一个个方法
3. TestCase继承unnitest.TestCase类,并依赖相应的Page类来实现相应的test case步骤
4.2 PO对象以及整个项目结构
-
data
- 存放公共数据,例如用户名和密码
-
Public
- 设计全局方法和变量
例如:设计一个可以代表项目所在根目录的全局变量
1 # coding = utf-8 2 import os 3 class global_value(): 4 # 1. 项目所在的根目录 5 def get_project_dir(): 6 try: 7 project_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 8 print("The project_dir is :", project_dir) 9 return project_dir 10 except: 11 print("Cannot find the project directory") 12 13 PROJECT_DIR = get_project_dir()
如果在其他类中需要这个全局变量,则需要先导入该类,例如在run_test中导入
1 from public import GlobalValue 2 project_dir = GlobalValue.global_value.PROJECT_DIR
-
report
- 存放报告
-
Page 基类(BasePage)
- 设计了一个基本的Page类,以便所有的页面进行继承,该类标明了一个sub page类的基本功能和公共的功能。
1 # coding utf-8 2 """ 3 @ author: 宋博文 4 封装所有的功用方法,例如driver, url, FindElement 5 """ 6 from selenium import webdriver 7 import time 8 import os 9 from selenium.webdriver.support.ui import Select 10 from selenium.webdriver.support.wait import WebDriverWait 11 from selenium.webdriver.common.action_chains import ActionChains 12 from public import GlobalValue 13 project_dir = GlobalValue.global_value.PROJECT_DIR 14 15 class base_page(): 16 17 # 初始化driver、url、pagetitle等 18 # 实例化BasePage类时,最先执行的就是__init__方法,该方法的入参,其实就是BasePage类的入参。 19 # __init__方法不能有返回值,只能返回None 20 # self指实例本身,相较于类-Page而言。 21 22 """ 23 构造方法 24 :param driver: 封装好的webdriver 25 :param base_url: 基本url:"http://www.pzcnet.com/" 26 :param PROJECT_DIR: 项目所在目录 27 """ 28 def __init__(self): 29 """ 30 # 定义driver和base_url 31 """ 32 driver = webdriver.Firefox() 33 try: 34 self.driver = driver 35 except Exception: 36 raise NameError("Firefox Not Found!") 37 38 self.base_url = "http://www.pzcnet.com/" 39 40 def captureScreenshot(self, filename): 41 # 截取屏幕截图并保存到指定路径 42 try: 43 self.driver.get_screenshot_as_file(filename) 44 except BaseException as e: 45 print('保存屏幕截图失败,失败信息:' + e) 46 47 48 def operationCheck(self, Name, isSucceed): 49 ''' 50 判断运行是否成功: 51 1、若成功,输出运行成功; 52 2、若不成功,则对失败页面进行屏幕截图,并保存: 53 (1)保存路径为'screenshot'--day_now--Name_time_now.png; 54 (2)保存前先判断'screenshot'文件夹下是否存在day_now文件夹,若不存在,则先新建'day_now'文件夹 55 (3)保存的文件名为 “所检查的操作名Name”_“所检查的时间time_now”.png 56 ''' 57 # 判断运行是否成功 58 if isSucceed: 59 print(Name + u":运行成功!") 60 61 else: 62 day_now = time.strftime("%Y%m%d", time.localtime(time.time())) 63 time_now = time.strftime("%H%M%S", time.localtime(time.time())) 64 path = os.path.join(project_dir, 'screenshot', day_now) 65 if not os.path.exists(path): 66 os.mkdir(path) 67 png_path = os.path.join(path, Name + "_" + time_now + '.png') 68 self.captureScreenshot(png_path) 69 print(Name + u":运行失败!请查看截图快照:" + png_path) 70 self.driver.quit() 71 72 def openPage(self, url): 73 """ 74 打开页面,通过拼接URL的方式 75 :param url: 76 :return: 77 """
-
Sub Pages(s)子类
- 具体的页面的类,定义了某个具体的页面的功能。
例如:在盘中餐网站中实现消费者的登录,主要功能未:消费者输入用户名-密码-验证码-点击“登录”-跳转到订餐首页
1. 引入BasePage中的所有基本方法
1 from pages import BasePage
2. 根据该页面的具体功能设计方法,例如消费者登录页面(CustomerLogin.py)
1 class customer_login(BasePage.base_page): 2 def __init__(self): 3 BasePage.base_page.__init__(self) 4 self.customer_login_page_url = 'customer/toLogin' 5 self.order_main_page_url = 'customer/toIndex' 6 7 def customer_account_login(self, user_name, user_password, captcha): 8 self.openPage(self.customer_login_page_url) 9 self.sendkeys("id,login-username", user_name) 10 self.sendkeys("id,login-password", user_password) 11 self.sendkeys("id,checkCode",captcha) 12 self.click("name,commit") 13 14 def get_orderMainPage(self): 15 return self.base_url + self.order_main_page_url
-
Tests 类
- 这部分描述的是具体的测试用例。
- 一个页面对应一个测试用例
- 测试用例都以test_开头,因为在使用unittest.main()时,默认会执行所有以test开头的测试用例
例如:对于消费者登录页面(CustomerLogin.py)对应一个测试用例(test_CustomerLogin.py)
1. 继承对应页面(CustomerLogin.py)中所有的方法
1 from pages import CustomerLogin
2. 声明一个操作员operator,用操作员来调用BasePage中的方法
1 # coding = utf-8 2 3 import unittest 4 from pages import CustomerLogin 5 6 class CustomerLoginTest(unittest.TestCase): 7 8 #在用例类中,只需要声明一个操作员(operator),用操作员来调用basepage类中的方法便可 9 def setUp(self): 10 self.operator = CustomerLogin.customer_login() 11 12 def test_customer_account_login(self): 13 u"""消费者登录""" 14 operator = self.operator 15 user_name = "xxxx" 16 user_password = "xxxx" 17 captcha = "xxxx" 18 operator.customer_account_login(user_name, user_password, captcha) 19 operator.get_orderMainPage() 20 21 def tearDown(self): 22 pass 23 24 if __name__ == "__main__": 25 unittest.main()
-
run_test 执行所用用例
1. 引入全局变量或方法:
1 from public import GlobalValue 2 project_dir = GlobalValue.global_value.PROJECT_DIR 3 testcase_dir = project_dir + "\\testcase"
2. 将测试目录testcase添加到系统的path
1 import sys 2 sys.path.append(testcase_dir)
3. 定义Test Suite
-
- 用discover()方法找到testcase下面所有的测试用例
- 将discover筛选出的测试用例,循环添加到测试套件中
1 import unittest 2 import HTMLTestRunner 3 import time 4 5 def create_suite(): 6 7 test_unit = unittest.TestSuite() 8 9 # 1. discover()方法定义,找到testcase目录下面所有的用例 10 # TestLoader:测试用例加载器,包含多个加载测试用例的方法,返回一个测试套件 11 # 其中的discover方法:discover(start_dir,pattern='test*.py',top_level_dir=None 12 # 到指定目录下所有测试模块,并可递归查到子目录下的测试模块,只有匹配到文件名才能被加载 13 discover = unittest.defaultTestLoader.discover( 14 testcase_dir, 15 pattern='test_*.py', 16 # top_level_dir=None 17 ) 18 19 # 2. 将discover筛选出的用例,循环添加到测试套件中 20 for test_suit in discover: 21 for test_case in test_suit: 22 test_unit.addTest(test_case) 23 print(test_unit) 24 return test_unit
4. 生成HTML测试报告,(并通过邮件发送到指定邮箱——暂未实现)
1 # 创建测试报告文件 2 def run_and_generate_report(test_object): 3 now = time.strftime('%Y-%m-%d_%H_M_S', time.localtime(time.time())) 4 filename = project_dir + "\\report\\" + now + 'report.html' 5 fp = open(filename,'wb') 6 7 runner = HTMLTestRunner.HTMLTestRunner( 8 stream=fp, 9 title='测试报告', 10 description='用例执行情况:' 11 )
5. 定义测试的主入口
-
- 定义测试的主要入口类,代码的入口
1 all_testSuit_names = create_suite() 2 run_and_generate_report(all_testSuit_names)