软件测试入门⑤——测试编程【讲义】

Day 5 测试编程讲义

Day 5 测试编程讲义

本篇讲义非常偏技术属性,需要用到比较多编程知识。阅读此讲义,需要对以下技术有一定了解:

  • 数据库表存储
  • 循环与分支语句
  • 基础设计模式
  • 面向对象封装
  • Python 库的使用

0 主要内容

  • 1 T8_测试数据驱动
  • 2 T9_测试业务抽离
  • 3 T10_底层驱动封装

1 T8_测试数据驱动

1.1 什么是数据驱动

  • 什么是数据驱动

    主要的数据驱动方式有两种:

    • 通过 文本文件或者 Excel 文件存储数据,并通过程序读取数据,遍历所有的行
    • 通过数据库存储数据,并通过程序和 SQL 脚本读取数据,遍历所有的行

    通过 CSV 文件 或者 MySQL 数据库,是主流的数据驱动方式。当然数据驱动也可以结合单元测试框架的参数化测试进行编写(此部分本文不做具体描述)。

    无论使用了 哪一种(CSV 或者 MySQL),读取数据后都要进行遍历操作。

1.2 使用 csv

csv 是一种纯文本的格式,主要用来存储数据。

import csv

csv_file = open("xxx.csv", "r", encoding="utf8")
csv_data = csv.reader(csv_file)
for row in csv_data:
    # 进行测试
    # 使用字典类型
    data_to_test = {
      "key1": row[0],
      "key2": row[1]
    }

csv_file.close()

1.3 使用 MySQL

import pymysql

connect = pymysql.connect(host="xx", port=3306, user="root", passwd="xxx", db="xx")
cur = connect.cursor()
cur.execute("SELECT...")
mysql_data = cur.fetchall()
for row in mysql_data:
    # 进行测试
    # 使用字典类型
    data_to_test = {
      "key1": row[0],
      "key2": row[1]
    }
    
cur.close()
connect.close()
  • 需要掌握的知识点:
    1. python的字典类型 dict 类型
    2. python的读写文件
    3. python的读写数据库
    4. for循环
    5. 注意资源的释放
      1. 关闭数据库游标和连接
      2. 关闭文件

2 T9_测试业务抽离

2.1 Page-Object设计模式本质

  • Page-Object设计模式的本质

    Page Object设计模式是Selenium自动化测试项目的最佳设计模式之一,强调测试、逻辑、数据和驱动相互分离。

    Page Object模式是Selenium中的一种测试设计模式,主要是将每一个页面设计为一个Class,其中包含页面中需要测试的元素(按钮,输入框,标题等),这样在Selenium测试页面中可以通过调用页面类来获取页面元素,这样巧妙的避免了当页面元素id或者位置变化时,需要改测试页面代码的情况。当页面元素id变化时,只需要更改测试页Class中页面的属性即可。

    它的好处如下:

    • 集中管理元素对象,便于应对元素的变化
    • 集中管理一个page内的公共方法,便于测试用例的编写
    • 后期维护方便,不需要重复的复制和修改代码

    具体的做法如下:

    1. 创建一个页面的类
    2. 在类的构造方法中,传递 WebDriver 参数。
    3. 在测试用例的类中,实例化页面的类,并且传递在测试用例中已经实例化的WebDriver对象。
    4. 在页面的类中,编写该页面的所有操作的方法
    5. 在测试用例的类中,调用这些方法

2.2 Page 如何划分

一般通过继承的方式,进行按照实际Web页面进行划分。

  • 主页
  • 子模块主页
    • 分类页
    • 详情页
      • 查看
      • 编辑
      • 增加

2.3 Page-Object 类如何实现

实现的示例

  • Page 基类

    • 设计了一个基本的 Page类,以便所有的页面进行继承,该类标明了一个sub page类的基本功能和公共的功能。

    • 全局变量: self.base_driver,让所有的子类都使用的。

      # 基类的变量,所有继承的类,都可以使用
      base_driver = None
      
    • 构造方法:

      • 传递 driver的构造方法

        # 方法
        def __init__(self, driver: BoxDriver):
            """
            构造方法
            :param driver: ":BoxDriver" 规定了 driver 参数类型
            """
            self.base_driver = driver
        
    • 私有的常量:存放元素的定位符

      LOGIN_ACCOUNT_SELECTOR = "s, #account"
      LOGIN_PASSWORD_SELECTOR = "s, #password"
      LOGIN_KEEP_SELECTOR = "s, #keepLoginon"
      LOGIN_SUBMIT_SELECTOR = "s, #submit"
      LOGIN_LANGUAGE_BUTTON_SELECTOR = "s, #langs > button"
      LOGIN_LANGUAGE_MENU_SELECTOR = "s, #langs > ul > li:nth-child(%d) > a"
      LOGIN_FAIL_MESSAGE_SELECTOR = "s, body > div.bootbox.modal.fade.bootbox-alert.in > div > div > div.modal-body"
      
    • 成员方法:

      • 每个子类都需要的系统功能:

        • open

          def open(self, url):
              """
              打开页面
              :param url:
              :return:
              """
              self.base_driver.navigate(url)
              self.base_driver.maximize_window()
              sleep(2)
          
      • 所有子类(页面)都具有的业务功能

        • select_app
        • logout
  • Sub Pages(s)子类

    • 具体的页面的类,定义了某个具体的页面的功能

    • 必须继承基类

      class MainPage(BasePage):
      
    • 特定页面的业务

    • 使用基类的 self.base_driver 成员变量

  • Tests 类

    • 这部分描述的是具体的测试用例。

    • 声明全局变量

      base_driver = None
      base_url = None
      main_page = None
      
    • 调用各种页面(pages)

      1. 实例化Page

        self.main_page = MainPage(self.base_driver)
        
      2. 使用page的对象,调用成员方法

        self.main_page.open(self.base_url)
        self.main_page.change_language(lang)
        

3 T10_底层驱动封装

3.1 为什么需要封装 Selenium

  • 什么是封装

    封装是一个面向对象编程的概念,是面向对象编程的核心属性,通过将代码内部实现进行密封和包装,从而简化编程。对Selenium进行封装的好处主要有如下三个方面:

    • 使用成本低
      1. 不需要要求所有的测试工程师会熟练使用Selenium,而只需要会使用封装以后的代码
      2. 不需要对所有的测试工程师进行完整培训。也避免工作交接的成本。
      3. 测试人员使用统一的代码库
    • 维护成本低
      1. 通过封装,在代码发生大范围变化和迁移的时候,不需要维护所有代码,只需要变更封装的部分即可
      2. 维护代码不需要有大量的工程师,只需要有核心的工程师进行封装的维护即可
    • 代码安全性
      1. 对作为第三方的Selenium进行封装,是代码安全的基础。
      2. 对于任何的代码的安全隐患,必须由封装来解决,使得风险可控。
      3. 使用者并不知道封装内部的代码结构。

3.2 封装的概念与基本操作

  • 关键方法的封装思路

    封装的具体示例:

    • 找到一个指定输入框(selector),并且输入指定的字符(text)

      type(selector, text)

      不用在业务逻辑中,使用多次的 find_element_by_id(...))

      def type(self, selector, text):
          """
          Operation input box.
          Usage:
          driver.type("i,el","selenium")
          """
          el = self._locate_element(selector)
          el.clear()
          el.send_keys(text)
      
    • 找到一个可以点击的元素(selector),并且点击(click)

      click(selector)

      def click(self, selector):
          """
          It can click any text / image can be clicked
          Connection, check box, radio buttons, and even drop-down box etc..
      
          Usage:
          driver.click("i,el")
          """
          el = self._locate_element(selector)
          el.click()
      
    • 找到一个指定的frame,并且切换进去

      switch_to_frame(selector)

      def switch_to_frame(self, selector):
          """
          Switch to the specified frame.
          Usage:
          driver.switch_to_frame("i,el")
          """
          el = self._locate_element(selector)
          self.base_driver.switch_to.frame(el)
      
    • 找到一个指定的select,并且通过index进行选择

      select_by_index(selector, index)

      def select_by_index(self, selector, index):
          """
          It can click any text / image can be clicked
          Connection, check box, radio buttons, and even drop-down box etc..
      
          Usage:
          driver.select_by_index("i,el")
          """
          el = self._locate_element(selector)
          Select(el).select_by_index(index)
      

    以上的代码是封装了_locate_element()的几种方法,在具体使用封装过的代码的时候,只需要简单的调用即可。接下来的重点,是介绍 _locate_element(selector)的封装方式。

    • 查找元素:find_element_by_...)
    • 支持各种的查找:8种方式都需要支持,必须通过 selector 显示出分类
      • selector中需要包含一个特殊符号
      • 实例化 封装好的类的时候,需要约定好是什么特殊符号
        1. 强制性用硬编码 hard code来实例化,例如 , 或者 ? 或者 其他非常用字符 =>
        2. 或者,构造方法中,传递 this.byChar
    • 要把查找到元素的返回给调用的地方:必须要有返回值,类型是 WebElement
        def _locate_element(self, selector):
            """
            to locate element by selector
            :arg
            selector should be passed by an example with "i,xxx"
            "x,//*[@id='langs']/button"
            :returns
            DOM element
            """
            if self.by_char not in selector:
                return self.base_driver.find_element_by_id(selector)
    
            selector_by = selector.split(self.by_char)[0].strip()
            selector_value = selector.split(self.by_char)[1].strip()
            if selector_by == "i" or selector_by == 'id':
                element = self.base_driver.find_element_by_id(selector_value)
            elif selector_by == "n" or selector_by == 'name':
                element = self.base_driver.find_element_by_name(selector_value)
            elif selector_by == "c" or selector_by == 'class_name':
                element = self.base_driver.find_element_by_class_name(selector_value)
            elif selector_by == "l" or selector_by == 'link_text':
                element = self.base_driver.find_element_by_link_text(selector_value)
            elif selector_by == "p" or selector_by == 'partial_link_text':
                element = self.base_driver.find_element_by_partial_link_text(selector_value)
            elif selector_by == "t" or selector_by == 'tag_name':
                element = self.base_driver.find_element_by_tag_name(selector_value)
            elif selector_by == "x" or selector_by == 'xpath':
                element = self.base_driver.find_element_by_xpath(selector_value)
            elif selector_by == "s" or selector_by == 'css_selector':
                element = self.base_driver.find_element_by_css_selector(selector_value)
            else:
                raise NameError("Please enter a valid type of targeting elements.")
    
            return element
    

  • 面向对象编程思想的运用

    • 构造方法
    • 普通方法
  • 封装后的方法如何被调用

    使用上面的封装类,就需要指定特定的 selector

    类型 示例(分隔符以逗号,为例) 描述
    id "account" 或者 "i,account" 或者 "id,account" 分隔符左右两侧不可以空格
    xpath "x,//*[@id="s-menu-dashboard"]/button/i"
    css selector "s,#s-menu-dashboard > button > i"
    link text "l,退出"
    partial link text "p,退"
    name "n,name1"
    tag name "t,input"
    class name "c,dock-bottom

    具体调用示例

    def login(self, account, password, keep):
        """
        登录系统
        :param account:
        :param password:
        :param keep:
        :return: 返回保持登录复选框的 checked 值
        """
        self.base_driver.type(self.LOGIN_ACCOUNT_SELECTOR, account)
        self.base_driver.type(self.LOGIN_PASSWORD_SELECTOR, password)
    
        current_checked = self.get_current_keep_value()
        if keep:
            if current_checked is None:
                self.base_driver.click(self.LOGIN_KEEP_SELECTOR)
        else:
            if current_checked == "true":
                self.base_driver.click(self.LOGIN_KEEP_SELECTOR)
    
        actual_checked = self.get_current_keep_value()
        self.base_driver.click(self.LOGIN_SUBMIT_SELECTOR)
        sleep(2)
        return actual_checked
    

3.3 测试报告的生成

  • 如何生成测试报告

  • 测试报告的种类

  • HTML 测试报告的生成

    HTML测试报告需要引入HTMLTestRunner

    HTMLTestRunner是基于Python2.7的,我们的课程讲义基于Python3.x,那么需要对这个文件做一定的修改。

    测试的示例代码如下

    # 声明一个测试套件
    suite = unittest.TestSuite()
    # 添加测试用例到测试套件
    suite.addTest(RanzhiTests("test_ranzhi_login"))
    
    # 创建一个新的测试结果文件
    buf = open("./result.html", "wb")
    
    # 声明测试运行的对象
    runner = HTMLTestRunner.HTMLTestRunner(stream=buf,
                                           title="Ranzhi Test Result",
                                           description="Test Case Run Result")
    # 运行测试,并且将结果生成为HTML
    runner.run(suite)
    
    # 关闭文件输出
    buf.close()
    
六天入门软件测试系列课程总纲
  • 相关学习

立师兄Linty:六天入门软件测试①——测试执行讲义

立师兄Linty:六天入门软件测试①——测试执行笔记

立师兄Linty:六天入门软件测试②——测试分析讲义

立师兄Linty:六天入门软件测试②——测试分析笔记

立师兄Linty:六天入门软件测试③——测试设计讲义

立师兄Linty:六天入门软件测试③——测试设计笔记

立师兄Linty:六天入门软件测试④——测试脚本讲义

立师兄Linty:六天入门软件测试④——测试脚本笔记

立师兄Linty:六天入门软件测试⑤——测试编程讲义

立师兄Linty:六天入门软件测试⑤——测试编程笔记

立师兄Linty:六天入门软件测试⑥——测试报告讲义

立师兄Linty:六天入门软件测试⑥——测试报告笔记

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

推荐阅读更多精彩内容