軟件測試入門⑤——測試編程【講義】

Day 5 測試編程講義

Day 5 測試編程講義

本篇講義非常偏技術(shù)屬性席怪,需要用到比較多編程知識。閱讀此講義盅粪,需要對以下技術(shù)有一定了解:

  • 數(shù)據(jù)庫表存儲
  • 循環(huán)與分支語句
  • 基礎(chǔ)設(shè)計(jì)模式
  • 面向?qū)ο蠓庋b
  • Python 庫的使用

0 主要內(nèi)容

  • 1 T8_測試數(shù)據(jù)驅(qū)動
  • 2 T9_測試業(yè)務(wù)抽離
  • 3 T10_底層驅(qū)動封裝

1 T8_測試數(shù)據(jù)驅(qū)動

1.1 什么是數(shù)據(jù)驅(qū)動

  • 什么是數(shù)據(jù)驅(qū)動

    主要的數(shù)據(jù)驅(qū)動方式有兩種:

    • 通過 文本文件或者 Excel 文件存儲數(shù)據(jù)蝇裤,并通過程序讀取數(shù)據(jù)全封,遍歷所有的行
    • 通過數(shù)據(jù)庫存儲數(shù)據(jù),并通過程序和 SQL 腳本讀取數(shù)據(jù)榄檬,遍歷所有的行

    通過 CSV 文件 或者 MySQL 數(shù)據(jù)庫卜范,是主流的數(shù)據(jù)驅(qū)動方式。當(dāng)然數(shù)據(jù)驅(qū)動也可以結(jié)合單元測試框架的參數(shù)化測試進(jìn)行編寫(此部分本文不做具體描述)鹿榜。

    無論使用了 哪一種(CSV 或者 MySQL)海雪,讀取數(shù)據(jù)后都要進(jìn)行遍歷操作。

1.2 使用 csv

csv 是一種純文本的格式舱殿,主要用來存儲數(shù)據(jù)奥裸。

import csv

csv_file = open("xxx.csv", "r", encoding="utf8")
csv_data = csv.reader(csv_file)
for row in csv_data:
    # 進(jìn)行測試
    # 使用字典類型
    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:
    # 進(jìn)行測試
    # 使用字典類型
    data_to_test = {
      "key1": row[0],
      "key2": row[1]
    }
    
cur.close()
connect.close()
  • 需要掌握的知識點(diǎn):
    1. python的字典類型 dict 類型
    2. python的讀寫文件
    3. python的讀寫數(shù)據(jù)庫
    4. for循環(huán)
    5. 注意資源的釋放
      1. 關(guān)閉數(shù)據(jù)庫游標(biāo)和連接
      2. 關(guān)閉文件

2 T9_測試業(yè)務(wù)抽離

2.1 Page-Object設(shè)計(jì)模式本質(zhì)

  • Page-Object設(shè)計(jì)模式的本質(zhì)

    Page Object設(shè)計(jì)模式是Selenium自動化測試項(xiàng)目的最佳設(shè)計(jì)模式之一,強(qiáng)調(diào)測試沪袭、邏輯湾宙、數(shù)據(jù)和驅(qū)動相互分離。

    Page Object模式是Selenium中的一種測試設(shè)計(jì)模式冈绊,主要是將每一個頁面設(shè)計(jì)為一個Class侠鳄,其中包含頁面中需要測試的元素(按鈕,輸入框死宣,標(biāo)題等)伟恶,這樣在Selenium測試頁面中可以通過調(diào)用頁面類來獲取頁面元素,這樣巧妙的避免了當(dāng)頁面元素id或者位置變化時毅该,需要改測試頁面代碼的情況知押。當(dāng)頁面元素id變化時叹螟,只需要更改測試頁Class中頁面的屬性即可。

    它的好處如下:

    • 集中管理元素對象台盯,便于應(yīng)對元素的變化
    • 集中管理一個page內(nèi)的公共方法罢绽,便于測試用例的編寫
    • 后期維護(hù)方便,不需要重復(fù)的復(fù)制和修改代碼

    具體的做法如下:

    1. 創(chuàng)建一個頁面的類
    2. 在類的構(gòu)造方法中静盅,傳遞 WebDriver 參數(shù)良价。
    3. 在測試用例的類中,實(shí)例化頁面的類蒿叠,并且傳遞在測試用例中已經(jīng)實(shí)例化的WebDriver對象明垢。
    4. 在頁面的類中,編寫該頁面的所有操作的方法
    5. 在測試用例的類中市咽,調(diào)用這些方法

2.2 Page 如何劃分

一般通過繼承的方式痊银,進(jìn)行按照實(shí)際Web頁面進(jìn)行劃分。

  • 主頁
  • 子模塊主頁
    • 分類頁
    • 詳情頁
      • 查看
      • 編輯
      • 增加

2.3 Page-Object 類如何實(shí)現(xiàn)

實(shí)現(xiàn)的示例

  • Page 基類

    • 設(shè)計(jì)了一個基本的 Page類施绎,以便所有的頁面進(jìn)行繼承溯革,該類標(biāo)明了一個sub page類的基本功能和公共的功能。

    • 全局變量: self.base_driver谷醉,讓所有的子類都使用的致稀。

      # 基類的變量,所有繼承的類俱尼,都可以使用
      base_driver = None
      
    • 構(gòu)造方法:

      • 傳遞 driver的構(gòu)造方法

        # 方法
        def __init__(self, driver: BoxDriver):
            """
            構(gòu)造方法
            :param driver: ":BoxDriver" 規(guī)定了 driver 參數(shù)類型
            """
            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"
      
    • 成員方法:

      • 每個子類都需要的系統(tǒng)功能:

        • open

          def open(self, url):
              """
              打開頁面
              :param url:
              :return:
              """
              self.base_driver.navigate(url)
              self.base_driver.maximize_window()
              sleep(2)
          
      • 所有子類(頁面)都具有的業(yè)務(wù)功能

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

    • 具體的頁面的類抖单,定義了某個具體的頁面的功能

    • 必須繼承基類

      class MainPage(BasePage):
      
    • 特定頁面的業(yè)務(wù)

    • 使用基類的 self.base_driver 成員變量

  • Tests 類

    • 這部分描述的是具體的測試用例。

    • 聲明全局變量

      base_driver = None
      base_url = None
      main_page = None
      
    • 調(diào)用各種頁面(pages)

      1. 實(shí)例化Page

        self.main_page = MainPage(self.base_driver)
        
      2. 使用page的對象遇八,調(diào)用成員方法

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

3 T10_底層驅(qū)動封裝

3.1 為什么需要封裝 Selenium

  • 什么是封裝

    封裝是一個面向?qū)ο缶幊痰母拍蠲妫敲嫦驅(qū)ο缶幊痰暮诵膶傩裕ㄟ^將代碼內(nèi)部實(shí)現(xiàn)進(jìn)行密封和包裝刃永,從而簡化編程蔑歌。對Selenium進(jìn)行封裝的好處主要有如下三個方面:

    • 使用成本低
      1. 不需要要求所有的測試工程師會熟練使用Selenium,而只需要會使用封裝以后的代碼
      2. 不需要對所有的測試工程師進(jìn)行完整培訓(xùn)揽碘。也避免工作交接的成本次屠。
      3. 測試人員使用統(tǒng)一的代碼庫
    • 維護(hù)成本低
      1. 通過封裝,在代碼發(fā)生大范圍變化和遷移的時候雳刺,不需要維護(hù)所有代碼劫灶,只需要變更封裝的部分即可
      2. 維護(hù)代碼不需要有大量的工程師,只需要有核心的工程師進(jìn)行封裝的維護(hù)即可
    • 代碼安全性
      1. 對作為第三方的Selenium進(jìn)行封裝掖桦,是代碼安全的基礎(chǔ)本昏。
      2. 對于任何的代碼的安全隱患,必須由封裝來解決枪汪,使得風(fēng)險可控涌穆。
      3. 使用者并不知道封裝內(nèi)部的代碼結(jié)構(gòu)怔昨。

3.2 封裝的概念與基本操作

  • 關(guān)鍵方法的封裝思路

    封裝的具體示例:

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

      type(selector, text)

      不用在業(yè)務(wù)邏輯中宿稀,使用多次的 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)
      
    • 找到一個可以點(diǎn)擊的元素(selector)趁舀,并且點(diǎn)擊(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,并且切換進(jìn)去

      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進(jìn)行選擇

      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()的幾種方法矮烹,在具體使用封裝過的代碼的時候,只需要簡單的調(diào)用即可罩锐。接下來的重點(diǎn)奉狈,是介紹 _locate_element(selector)的封裝方式。

    • 查找元素:find_element_by_...)
    • 支持各種的查找:8種方式都需要支持涩惑,必須通過 selector 顯示出分類
      • selector中需要包含一個特殊符號
      • 實(shí)例化 封裝好的類的時候仁期,需要約定好是什么特殊符號
        1. 強(qiáng)制性用硬編碼 hard code來實(shí)例化,例如 , 或者 ? 或者 其他非常用字符 =>
        2. 或者竭恬,構(gòu)造方法中跛蛋,傳遞 this.byChar
    • 要把查找到元素的返回給調(diào)用的地方:必須要有返回值,類型是 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
    

    ?

  • 面向?qū)ο缶幊趟枷氲倪\(yùn)用

    • 構(gòu)造方法
    • 普通方法
  • 封裝后的方法如何被調(diào)用

    使用上面的封裝類萍聊,就需要指定特定的 selector

    類型 示例(分隔符以逗號,為例) 描述
    id "account" 或者 "i,account" 或者 "id,account" 分隔符左右兩側(cè)不可以空格
    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

    具體調(diào)用示例

    def login(self, account, password, keep):
        """
        登錄系統(tǒng)
        :param account:
        :param password:
        :param keep:
        :return: 返回保持登錄復(fù)選框的 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 測試報(bào)告的生成

  • 如何生成測試報(bào)告

  • 測試報(bào)告的種類

  • HTML 測試報(bào)告的生成

    HTML測試報(bào)告需要引入HTMLTestRunner

    HTMLTestRunner是基于Python2.7的问芬,我們的課程講義基于Python3.x悦析,那么需要對這個文件做一定的修改寿桨。

    測試的示例代碼如下

    # 聲明一個測試套件
    suite = unittest.TestSuite()
    # 添加測試用例到測試套件
    suite.addTest(RanzhiTests("test_ranzhi_login"))
    
    # 創(chuàng)建一個新的測試結(jié)果文件
    buf = open("./result.html", "wb")
    
    # 聲明測試運(yùn)行的對象
    runner = HTMLTestRunner.HTMLTestRunner(stream=buf,
                                           title="Ranzhi Test Result",
                                           description="Test Case Run Result")
    # 運(yùn)行測試,并且將結(jié)果生成為HTML
    runner.run(suite)
    
    # 關(guān)閉文件輸出
    buf.close()
    
六天入門軟件測試系列課程總綱
  • 相關(guān)學(xué)習(xí)

立師兄Linty:六天入門軟件測試①——測試執(zhí)行講義

立師兄Linty:六天入門軟件測試①——測試執(zhí)行筆記

立師兄Linty:六天入門軟件測試②——測試分析講義

立師兄Linty:六天入門軟件測試②——測試分析筆記

立師兄Linty:六天入門軟件測試③——測試設(shè)計(jì)講義

立師兄Linty:六天入門軟件測試③——測試設(shè)計(jì)筆記

立師兄Linty:六天入門軟件測試④——測試腳本講義

立師兄Linty:六天入門軟件測試④——測試腳本筆記

立師兄Linty:六天入門軟件測試⑤——測試編程講義

立師兄Linty:六天入門軟件測試⑤——測試編程筆記

立師兄Linty:六天入門軟件測試⑥——測試報(bào)告講義

立師兄Linty:六天入門軟件測試⑥——測試報(bào)告筆記

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末强戴,一起剝皮案震驚了整個濱河市亭螟,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌骑歹,老刑警劉巖预烙,帶你破解...
    沈念sama閱讀 206,013評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異道媚,居然都是意外死亡扁掸,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,205評論 2 382
  • 文/潘曉璐 我一進(jìn)店門最域,熙熙樓的掌柜王于貴愁眉苦臉地迎上來谴分,“玉大人,你說我怎么就攤上這事镀脂∥悖” “怎么了?”我有些...
    開封第一講書人閱讀 152,370評論 0 342
  • 文/不壞的土叔 我叫張陵掰担,是天一觀的道長司忱。 經(jīng)常有香客問我,道長蜓肆,這世上最難降的妖魔是什么鼎天? 我笑而不...
    開封第一講書人閱讀 55,168評論 1 278
  • 正文 為了忘掉前任舀奶,我火速辦了婚禮,結(jié)果婚禮上训措,老公的妹妹穿的比我還像新娘伪节。我一直安慰自己,他們只是感情好绩鸣,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,153評論 5 371
  • 文/花漫 我一把揭開白布怀大。 她就那樣靜靜地躺著,像睡著了一般呀闻。 火紅的嫁衣襯著肌膚如雪化借。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 48,954評論 1 283
  • 那天捡多,我揣著相機(jī)與錄音蓖康,去河邊找鬼。 笑死垒手,一個胖子當(dāng)著我的面吹牛蒜焊,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播科贬,決...
    沈念sama閱讀 38,271評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼泳梆,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了榜掌?” 一聲冷哼從身側(cè)響起优妙,我...
    開封第一講書人閱讀 36,916評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎憎账,沒想到半個月后套硼,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,382評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡胞皱,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,877評論 2 323
  • 正文 我和宋清朗相戀三年邪意,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片反砌。...
    茶點(diǎn)故事閱讀 37,989評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡雾鬼,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出于颖,到底是詐尸還是另有隱情呆贿,我是刑警寧澤,帶...
    沈念sama閱讀 33,624評論 4 322
  • 正文 年R本政府宣布,位于F島的核電站做入,受9級特大地震影響冒晰,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜竟块,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,209評論 3 307
  • 文/蒙蒙 一壶运、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧浪秘,春花似錦蒋情、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,199評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至夺衍,卻和暖如春狈谊,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背沟沙。 一陣腳步聲響...
    開封第一講書人閱讀 31,418評論 1 260
  • 我被黑心中介騙來泰國打工河劝, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人矛紫。 一個月前我還...
    沈念sama閱讀 45,401評論 2 352
  • 正文 我出身青樓赎瞎,卻偏偏與公主長得像,于是被迫代替她去往敵國和親颊咬。 傳聞我的和親對象是個殘疾皇子务甥,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,700評論 2 345

推薦閱讀更多精彩內(nèi)容