关于自动化Python+Selenium的自动化测试实践——之PageObject篇(01)

网上现在有不少对于PageObject的说明和理解,可是从我看来,对待这种方式,每个人都有自己的理解方式,我自己其实也看了网上的一些介绍,但是说真的,有时候不是特别想去按照别人的思维方式来进行。不管别人的东西对不对。另外每个人的基础知识的储备不同,也让大家在对待一个问题的时候,理解的方向就会有所不同。那么作为个人,我的通俗非专业的理解如下:

  1. PO 模式 = pageobject
  2. PO 是一种通过不断分离数据(参数化),不断的分离出公用代码(模块化),不断的将业务和实现过程分离的一种非官方的一种方式。
  3. PO 是一种将page形成类和对象,统一管理的思路
    针对上面的自己个人理解和实际操作,这次介绍的是通过继承的方式来设计
    1. 首先介绍一下个人的代码文件夹结构和说明:
    image.png

如上图:
excel_doc : 该文件夹主要是用于存放execl的测试用例excel,以及针对与excel的一个py文件,用于对test用例的操作


image.png

log: 顾名思义,就是存放执行过程中的log日志,同时log的py文件也在其中
report:很好理解的一个东西,就是测试报告,通过htmltestrunner自动生成的
testcase: 该地方就是存储执行的用例的地方。


image.png

testproject:该地方主要就是存储baseclass ,以及对应的页面类
image.png

test_all_case: 测试执行的py文件
image.png

如上就是自己的测试用例文件夹目录,这东西不是固定的,其实个人感觉你完全可以根据自己的喜好来定义这个名称和目录,比如把page类,放在一个文件夹目录也是可以的,不用纠结与网上的各种目录结构。

整体设计思路如下:


image.png
  1. 如何开展代码,从那里开始着手?
    这个地方我要说一下,其实这个完全看个人的代码编写能力,如果是代码老员工的话,那么直接就可以从整个case的外环境编写,比如baseMethod,commonMethod,test-all-caese等,如果是新人,个人建议从testcase来写。然后不断的去参数化,模块化。
    我就按照我的方式来说明一下,比较综合:
    第一步: 封装自己需要到的一些基础webdriver中需要的方法
    selenium自带的webdriver的什么定位,输入值等都已经够用了。但是为了让自己实际使用起来更加符合自己的想法,我们需要对其进行二次的封装和重写,当然这里说的好像很高大上,但是就我的能力来说,就是为了看起来稍微简洁一点:
    举例部分BaseMethodClass类方法:
# coding=utf-8
__author__ = 'Administrator'
from selenium import webdriver
import time
import os
# import xlrd
# import xlwt
from selenium.webdriver.support.ui import WebDriverWait
# from selenium.common.exceptions import NoSuchElementException, TimeoutException
from selenium.webdriver.common.action_chains import ActionChains
class BaseMethodClass(object):
   """
       基础类方法、主要是用于写用户的基础操作函数,目前来说总的是对现在webdriver默认的函数进行改写
       变为更加易读的方式
   """
   # 设置需要的浏览器对象
   def set_browser(self, browser_name):
       if browser_name == 'chrome':
           self.browser = webdriver.Chrome()
       elif browser_name == "ie":
           self.browser = webdriver.Ie()
       elif browser_name == 'firefox':
           self.browser = webdriver.Firefox()
   # 退出该浏览器的下的对象
   def quit_browser(self):
       self.browser.quit()
   # 关闭浏览器
   def close_browser(self):
       self.browser.close()
   # 打开指定的网页并且最大化
   def open_web_and_maxsize(self, url):
       self.browser.get(url)
       self.browser.maximize_window()
   # 定位一个元素
   def find_element(self, type_name, location):
       try:
           if type_name == 'id':
               ele = WebDriverWait(self.browser, 10).until(
                   lambda browser: browser.find_element_by_id(location))
               return ele
               # return self.browser.find_element_by_id(location)
           elif type_name == 'css':
               ele = WebDriverWait(self.browser, 10).until(
                   lambda browser: browser.find_element_by_css_selector(location))
               return ele
               # return self.browser.find_element_by_css_selector(location)
           elif type_name == 'xpath':
               ele = WebDriverWait(self.browser, 10).until(
                   lambda browser: browser.find_element_by_xpath(location))
               return ele
               # return self.browser.find_element_by_xpath(location)
           elif type_name == 'classname':
               ele = WebDriverWait(self.browser, 10).until(
                   lambda browser: browser.find_element_by_class_name(location))
               return ele
               # return self.browser.find_element_by_class_name(location)
           elif type_name == 'name':
               ele = WebDriverWait(self.browser, 10).until(
                   lambda browser: browser.find_element_by_name(location))
               return ele
               # return self.browser.find_element_by_name(location)
           elif type_name == 'tagname':
               ele = WebDriverWait(self.browser, 10).until(
                   lambda browser: browser.find_element_by_tag_name(location))
               return ele
               # return self.browser.find_element_by_tag_name(location)
           elif type_name == 'part_linktext':
               ele = WebDriverWait(self.browser, 10).until(
                   lambda browser: browser.find_element_by_partial_link_text(location))
               return ele
               # return self.browser.find_element_by_partial_link_text(location)
           elif type_name == 'linktext':
               ele = WebDriverWait(self.browser, 10).until(
                   lambda browser: browser.find_element_by_link_text(location))
               return ele
               # return self.browser.find_element_by_link_text(location)
       except:
           print u"没有找到该 %s 元素位置:%s" % (type_name, location)

如上代码,base类中对于定位元素和退出浏览器等方法都进行了改装
CommonMethod模块
主要是写一些,例如邮件发送,找到case,生成报告文件等

# coding=utf-8
import smtplib
__author__ = 'Administrator'
import os
from BaseMethod import BaseMethodClass
import unittest
from email.mime.text import MIMEText
from email.header import Header
import time
# 函数用到的数据
# report_path_1 = os.path.abspath(os.path.join(os.getcwd(), '..\\report'))
report_path_1 = "C:\\Python27\\workspace\\study\\JC_autotest\\qhddfxlPO\\report"
"""从file_path文件夹中,筛选出所有的case,并且存放到testsuite套件中"""
def find_all_case_1(file_path):
    # 创建一个套件: 可理解为准备一个大袋子,用来装case这个苹果
    test_suites = unittest.TestSuite()
    # 发现了所有的case苹果,但是很乱
    discover_case = unittest.defaultTestLoader.discover(file_path,
                                                        pattern='test1*.py',
                                                        top_level_dir=None)
    # 将上面的未包装的苹果case统一放入到大袋子中
    for test_suite in discover_case:
        for test_case in test_suite:
            test_suites.addTest(test_case)
    # 返回这个袋子苹果
    return test_suites
"""从report文件夹中读取最新的一个测试报告"""
def find_newest_report_1(report_path):
    # 获得path下的所有文件
    # report_path = report_path_1
    lists = os.listdir(report_path)
    # print 'List1:', lists
    # 对当前文件夹下的所有文件进行排序
    lists.sort(key=lambda fx: os.path.getctime(report_path + "\\" + fx))
    # print 'List2:', lists
    report = lists[-1]
    # 返回当前最新的一个文件
    newest_report = os.path.join(report_path, report)
    return newest_report
"""不带附件的邮件"""
def send_mail_2(receiver_user, receiver_pwd):
    # 发送者邮箱
    sender = "wy956486535@126.com"
    # 接受者邮箱
    receiver = receiver_user
    # 通过XXsmtp的服务器
    smtpserver = 'smtp.126.com'
    # 发送邮件中的主题
    email_subject = u'XXXXX项目自动化测试运行报告'
    # 发送邮件中的内容
    email_content = find_newest_report_1(report_path_1)
    # 发送邮箱需要指定人员的授权名称和密码
    grant_person = receiver_user
    grant_pwd = receiver_pwds
    # 将或者的邮件内容转换成可读取状态
    fp1 = file(email_content, 'rb')
    content = fp1.read()
    # msg = email.MIMEText(content, _charset='utf-8', _subtype='html')
    msg = MIMEText(content, _charset="utf-8", _subtype="html")
    msg["subject"] = Header(email_subject, "utf-8")
    # 发送邮件
    smtp = smtplib.SMTP()
    smtp.connect(smtpserver)
    smtp.login(grant_person, grant_pwd)
    smtp.sendmail(sender, receiver, msg.as_string())
    smtp.quit()
"""生成动态时间测试报告"""
def makeA_report_1(reportpath):
    now = time.strftime("%Y-%m-%d-%H-%M-%S", time.localtime(time.time()))
    # reportpath1 = os.path.abspath(os.path.join(os.getcwd(), '..\\report'))
    report_path = reportpath + '\\' + now + "-" + "report.html"
    report_file = file(report_path, 'wb')
    return report_file

第二步:通过继承Base类,对Page页面中的功能进行封装


image.png

第三步:通过继承page类,进行页面的case设计,


image.png

由于通过是不断的继承,所以在后期的代码效果就是


image.png

对于case的管理,目前也有两方方式:
image.png

每个py文件就一个case(优点是,并且每个case都是通过py文件独立,互相之间影响小 ,但是对于系统测试点详细的时候,会出现大量的py文件,不适合后期管理,个人觉得比较适合那种关于流程性的case)


image.png

一个py文件中,执行多个case,(优点是生成的py文件少,一个py中就可以实现多case,比较好管理,缺点是,这种方式如果后期需要公用一个driver的时候,就比较难理解)

第四步:case完成后,就可以对testcase的执行执行设计,如下:

# coding=utf-8
__author__ = 'Administrator'
from testproject1.CommonMethod import *
from study.JC_autotest.qhddfxlPO.log import log
import HTMLTestRunner
import sys
reload(sys)
sys.setdefaultencoding('utf-8')
# import unittest
if __name__ == "__main__":
    title = u"青海电大测试报告"
    description = u"青海电大测试"
    file_path = "C:\\Python27\\workspace\\study\\JC_autotest\\qhddfxlPO\\testcase1"
    rep = makeA_report_1("C:\\Python27\\workspace\\study\\JC_autotest\\qhddfxlPO\\report")
    runner = HTMLTestRunner.HTMLTestRunner(
        stream=rep,
        title=title,
        description=description
    )
    # runner = unittest.TextTestRunner()
    all_testcases = find_all_case_1(file_path)
    runner.run(all_testcases)
    # 如果这个没有,那么就会出现在htmltestrunner执行的报告中写入的报告不全的情况
    rep.close()
    send_mail_2("wxxxxx@126.com", "wyxxxxx35")

上图中的,find_all_case 和 makeA_report 都是在commonmethod中的封装的方法,此处通过导入后,直接调用的

综上所属,基本上一个简单的通过继承关系得到的po就出来了。

个人感觉这个po的方便在于不管是Base类中的方法,还是page类中的方法,,都可以在你实现自己的case的业务脚本中调用,不会让你在case中,因为page未有对应的业务方法而去在page中去特意封装一个和base类一样的方法
但是也有人对这种的存在疑问: 因为case和page并不存在is a 这种子父类的关系,从某一方面来说这种设计是不合理的,但是有时候一个阶段,自己就会有一个阶段的自我认识,而当时我的思路就是这样的。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,864评论 6 494
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,175评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,401评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,170评论 1 286
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,276评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,364评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,401评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,179评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,604评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,902评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,070评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,751评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,380评论 3 319
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,077评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,312评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,924评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,957评论 2 351

推荐阅读更多精彩内容