UI自動化測試實戰之頁面對象設計模式(十二)


      在前面的技術文章中已經很系統的介紹了UI自動化測試的技術棧的知識體系,但是在維護的成本而言

還是需要考慮進一步的優化,那么我們可以使用頁面對象設計模式,它的優勢具體可以總結為如下:

  • 創建可以跨多個測試用例共享的代碼
  • 減少重復代碼的數量
  • 如果用戶界面發生了維護,我們只需要維護一個地方,這樣修改以及維護的成本相對而言是比較低的

一、目錄結構設計

           下面我們具體針對這部分的目錄進行設計,具體的目錄結構為:

下面我詳細的解釋下每個目錄的是干什么的,具體總結為如下:

  • base的包下主要編寫基礎的代碼,可以理解為基礎層
  • page包下主要存放對象層的代碼,可以理解為對象層
  • test報下主要存放編寫的測試模塊的代碼,可以理解為測試層
  • utils下存放的主要是工具類的代碼,比如針對JSON文件,YAML文件的處理
  • common下主要存放的是公共類的代碼,比如文件目錄的處理
  • data主要存放測試過程中使用到的數據
  • report主要存儲的是測試報告這部分

二、頁面對象設計模式

           上面已經很詳細的解釋了頁面對象設計模式它的優勢,以及目錄結構的設計,下面依次來實現各個部分的

代碼。

2.1、基礎層

         下面主要實現基礎層的代碼,在base包下創建模塊為basePage.py的文件,里面的源碼信息為:

#! /usr/bin/env python
# -*- coding:utf-8 -*-
#author:無涯

from selenium import  webdriver
from selenium.webdriver.support.expected_conditions import NoSuchElementException
from selenium.webdriver.common.by import By
import  time as t

class WebDriver(object):
   def __init__(self,driver):
      self.driver=driver


   def findElement(self,*loc):
      '''單個元素的定位方式'''
      try:
         return self.driver.find_element(*loc)
      except NoSuchElementException as e:
         return e.args[0]

   def findElements(self,*loc):
      '''多個元素的定位方式'''
      try:
         return self.driver.find_elements(*loc)
      except NoSuchElementException as e:
         return e.args[0]


2.2、對象層

           下面以sina的郵箱為案例來編寫具體的代碼,在page包下創建login.py的文件,里面的源碼具體為:

#! /usr/bin/env python
# -*- coding:utf-8 -*-
#author:無涯

from base.basePage import WebDriver
from selenium.webdriver.common.by import By

class Login(WebDriver):
   username=(By.ID,'freename')
   password=(By.ID,'freepassword')
   loginButton=(By.LINK_TEXT,'登錄')
   divText=(By.XPATH,'/html/body/div[3]/div/div[2]/div/div/div[4]/div[1]/div[1]/div[1]/span[1]')


   def setUsername(self,username):
      '''用戶名輸入框'''
      self.findElement(*self.username).send_keys(username)

   def setPassword(self,password):
      '''密碼輸入框'''
      self.findElement(*self.password).send_keys(password)

   @property
   def clickLogin(self):
      '''點擊登錄按鈕'''
      self.findElement(*self.loginButton).click()

   @property
   def getDivText(self):
      '''獲取錯誤信息'''
      return self.findElement(*self.divText).text

2.3、測試層

           下來在測試層,也就是test包下創建test_sina_login.py的模塊,原代碼具體為:

#! /usr/bin/env python
# -*- coding:utf-8 -*-
#author:無涯

from page.login import Login
from selenium import  webdriver
import  unittest
import  time as t


class TestSinaLogin(unittest.TestCase,Login):
   def setUp(self) -> None:
      self.driver=webdriver.Chrome()
      self.driver.maximize_window()
      self.driver.implicitly_wait(30)
      self.driver.get('https://mail.sina.com.cn/#')

   def tearDown(self) -> None:
      self.driver.quit()


   def test_login_null(self):
      '''登錄驗證:用戶名密碼為空的錯誤信息驗證'''
      self.setUsername('')
      self.setPassword('')
      self.clickLogin
      self.assertEqual(self.getDivText,'請輸入郵箱名')

   def test_login_email_format(self):
      '''登錄驗證:郵箱名格式不正確的驗證'''
      self.setUsername('aertydrty')
      self.setPassword('erstytry')
      self.clickLogin
      self.assertEqual(self.getDivText,'您輸入的郵箱名格式不正確')

   def test_login_username_password_error(self):
      '''登錄驗證:用戶名和密碼不匹配的錯誤信息驗證'''
      self.setUsername('srtyua@sina.com')
      self.setPassword('sertysrty')
      self.clickLogin
      self.assertEqual(self.getDivText,'登錄名或密碼錯誤')

if __name__ == '__main__':
   unittest.main(verbosity=2)

 切記,需要執行驗證下我們編寫的具體的測試用例。

2.4、公共方法

          下來在common包下創建public.py的模塊,里面主要編寫針對文件路徑的處理,具體源碼如下:

#! /usr/bin/env python
# -*- coding:utf-8 -*-
#author:無涯

import  os

def base_dir():
   return os.path.dirname(os.path.dirname(__file__))


def filePath(directory='data',fileName=None):
   return os.path.join(base_dir(),directory,fileName)

2.5、數據驅動

           下來在data的文件夾下創建sina.json的文件,把登錄使用到的數據分離到sina.json的文件里面,該文件

的具體內容為:

{
  "login": 
  {
    "null": "請輸入郵箱名",
    "format": "您輸入的郵箱名格式不正確",
    "loginError": "登錄名或密碼錯誤"
  }
}

2.6、工具類

            下來在具體的工具類中編寫針對JSON文件的處理,創建的模塊名稱為:operationJson.py,具體源碼為:

#! /usr/bin/env python
# -*- coding:utf-8 -*-
#author:無涯


from common.public import filePath
import  json

def readJson():
   return json.load(open(filePath(fileName='sina.json')))

print(readJson()['login']['null'])

2.7、測試固件分離

       我們已經達到了數據驅動的分離,下來針對測試固件進行分離,在page包下創建init.py文件,來具體

分離我們的測試固件,源碼信息為:

#! /usr/bin/env python
# -*- coding:utf-8 -*-
#author:無涯


from selenium import  webdriver
import  unittest

class Init(unittest.TestCase):
   def setUp(self) -> None:
      self.driver=webdriver.Chrome()
      self.driver.maximize_window()
      self.driver.implicitly_wait(30)
      self.driver.get('https://mail.sina.com.cn/#')

   def tearDown(self) -> None:
      self.driver.quit()

2.8、完善測試層

         已經針對測試固件和數據都做了分離,下來完善測試模塊里面的代碼,完善后的代碼具體為:

#! /usr/bin/env python
# -*- coding:utf-8 -*-
#author:無涯

from page.login import Login
from selenium import  webdriver
from page.init import Init
import  unittest
import  time as t
from utils.operationJson import readJson


class TestSinaLogin(Init,Login):

   def test_login_null(self):
      '''登錄驗證:用戶名密碼為空的錯誤信息驗證'''
      self.setUsername('')
      self.setPassword('')
      self.clickLogin
      self.assertEqual(self.getDivText,readJson()['login']['null'])

   def test_login_email_format(self):
      '''登錄驗證:郵箱名格式不正確的驗證'''
      self.setUsername('aertydrty')
      self.setPassword('erstytry')
      self.clickLogin
      self.assertEqual(self.getDivText,readJson()['login']['format'])

   def test_login_username_password_error(self):
      '''登錄驗證:用戶名和密碼不匹配的錯誤信息驗證'''
      self.setUsername('srtyua@sina.com')
      self.setPassword('sertysrty')
      self.clickLogin
      self.assertEqual(self.getDivText,readJson()['login']['loginError'])

if __name__ == '__main__':
   unittest.main(verbosity=2)

三、引人等待機制

            下面我們在基礎層的代碼中引入等待的機制,也就是顯式的等待記住,那么完善后的基礎層代碼具體就為:

#! /usr/bin/env python
# -*- coding:utf-8 -*-
#author:無涯

from selenium import  webdriver
from selenium.webdriver.support.expected_conditions import NoSuchElementException
from selenium.webdriver.common.by import By
from selenium.webdriver.support.wait import WebDriverWait
import  time as t

class WebDriver(object):
   def __init__(self,driver):
      self.driver=driver


   def findElement(self,*loc):
      '''單個元素的定位方式'''
      try:
         return WebDriverWait(self.driver,20).until(lambda x:x.find_element(*loc))
      except NoSuchElementException as e:
         return e.args[0]

   def findElements(self,*loc):
      '''多個元素的定位方式'''
      try:
         return WebDriverWait(self.driver,20).until(lambda x:x.find_elements(*loc))
      except NoSuchElementException as e:
         return e.args[0]


四、引人工廠設計模式

         在移動的測試框架Appium的源碼中,我們可以看到它的元素定位的類繼承了Selenium中的By類,具體

源碼為:

#!/usr/bin/env python

# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from selenium.webdriver.common.by import By


class MobileBy(By):
    IOS_PREDICATE = '-ios predicate string'
    IOS_UIAUTOMATION = '-ios uiautomation'
    IOS_CLASS_CHAIN = '-ios class chain'
    ANDROID_UIAUTOMATOR = '-android uiautomator'
    ANDROID_VIEWTAG = '-android viewtag'
    ANDROID_DATA_MATCHER = '-android datamatcher'
    ANDROID_VIEW_MATCHER = '-android viewmatcher'
    WINDOWS_UI_AUTOMATION = '-windows uiautomation'
    ACCESSIBILITY_ID = 'accessibility id'
    IMAGE = '-image'
    CUSTOM = '-custom'

根據這樣的一個繼承思想,我們完全可以把Appium測試框架和Selenium3的測試框架整合起來,這樣

不管是移動的平台還是WEB的平台,我們可以使用一套元素定位的方法,那么這個過程中我們可以引人

設計模式中的工廠設計模式,引人工廠設計模式后,這對基礎層的代碼進行完善,完善后的代碼為:

#! /usr/bin/env python
# -*- coding:utf-8 -*-
#author:無涯

from selenium import  webdriver
from selenium.webdriver.support.expected_conditions import NoSuchElementException
from selenium.webdriver.common.by import By
from selenium.webdriver.support.wait import WebDriverWait
from appium.webdriver.common.mobileby import MobileBy
import  time as t



class Factory(object):
   def __init__(self,driver):
      self.driver=driver

   def createDriver(self,driver):
      if driver=='web':
         return WEB(self.driver)
      elif driver=='app':
         return APP(self.driver)

class WebDriver(object):
   def __init__(self,driver):
      self.driver=driver


   def findElement(self,*loc):
      '''單個元素的定位方式'''
      try:
         return WebDriverWait(self.driver,20).until(lambda x:x.find_element(*loc))
      except NoSuchElementException as e:
         return e.args[0]

   def findElements(self,*loc):
      '''多個元素的定位方式'''
      try:
         return WebDriverWait(self.driver,20).until(lambda x:x.find_elements(*loc))
      except NoSuchElementException as e:
         return e.args[0]


class WEB(WebDriver):
   def __str__(self):
      return 'web'

class APP(WebDriver):
   def __str__(self):
      return 'app'

下來我們需要針對對象層的代碼進行改造和維護,也就是繼承WEB的類,而不再是WebDriver,具體修改后的

源碼為:

#! /usr/bin/env python
# -*- coding:utf-8 -*-
#author:無涯

from base.basePage import WebDriver,WEB
from selenium.webdriver.common.by import By

class Login(WEB):
   username=(By.ID,'freename')
   password=(By.ID,'freepassword')
   loginButton=(By.LINK_TEXT,'登錄')
   divText=(By.XPATH,'/html/body/div[3]/div/div[2]/div/div/div[4]/div[1]/div[1]/div[1]/span[1]')


   def setUsername(self,username):
      '''用戶名輸入框'''
      self.findElement(*self.username).send_keys(username)

   def setPassword(self,password):
      '''密碼輸入框'''
      self.findElement(*self.password).send_keys(password)

   @property
   def clickLogin(self):
      '''點擊登錄按鈕'''
      self.findElement(*self.loginButton).click()

   @property
   def getDivText(self):
      '''獲取錯誤信息'''
      return self.findElement(*self.divText).text

五、整合持續集成平台

         最后我們把編寫的測試框架整合到C I的持續集成平台,以及結合Pytest測試框架和第三方的測試工具

Allure來生成測試報告,具體Execute Sehll里面輸入的內容為:

cd /Applications/code/Yun/uiSevenFrame/test
python3 -m pytest -s -v test_sina_login.py --alluredir=${WORKSPACE}/report

構建后操作步驟選擇Allure Report,具體如下所示:

點擊構建后,執行的結果信息如下所示:

Started by user 無涯
Running as SYSTEM
Building in workspace /Users/liwangping/.jenkins/workspace/uiSeven
[uiSeven] $ /bin/sh -xe /Applications/devOps/CICD/apache-tomcat/temp/jenkins7666607542083974346.sh
+ cd /Applications/code/Yun/uiSevenFrame/test
+ python3 -m pytest -s -v test_sina_login.py --alluredir=/Users/liwangping/.jenkins/workspace/uiSeven/report
============================= test session starts ==============================
platform darwin -- Python 3.7.4, pytest-6.2.5, py-1.9.0, pluggy-0.13.1 -- /Library/Frameworks/Python.framework/Versions/3.7/bin/python3
cachedir: .pytest_cache
sensitiveurl: .*
metadata: {'Python': '3.7.4', 'Platform': 'Darwin-20.6.0-x86_64-i386-64bit', 'Packages': {'pytest': '6.2.5', 'py': '1.9.0', 'pluggy': '0.13.1'}, 'Plugins': {'instafail': '0.4.1.post0', 'forked': '1.0.2', 'asyncio': '0.15.1', 'variables': '1.9.0', 'emoji': '0.2.0', 'tavern': '1.12.2', 'sugar': '0.9.4', 'timeout': '1.3.3', 'xdist': '2.3.0', 'dependency': '0.5.1', 'mock': '3.6.1', 'base-url': '1.4.1', 'html': '2.1.1', 'django': '3.7.0', 'cov': '2.7.1', 'nameko': '2.13.0', 'repeat': '0.9.1', 'selenium': '2.0.1', 'trio': '0.7.0', 'Faker': '4.14.0', 'allure-pytest': '2.8.11', 'metadata': '1.8.0', 'rerunfailures': '10.0'}, 'BUILD_NUMBER': '3', 'BUILD_ID': '3', 'BUILD_URL': 'http://localhost:8080/jenkins/job/uiSeven/3/', 'NODE_NAME': 'master', 'JOB_NAME': 'uiSeven', 'BUILD_TAG': 'jenkins-uiSeven-3', 'EXECUTOR_NUMBER': '1', 'JENKINS_URL': 'http://localhost:8080/jenkins/', 'JAVA_HOME': '/Library/Java/JavaVirtualMachines/jdk1.8.0_241.jdk/Contents/Home', 'WORKSPACE': '/Users/liwangping/.jenkins/workspace/uiSeven', 'Base URL': '', 'Driver': None, 'Capabilities': {}}
rootdir: /Applications/code/Yun/uiSevenFrame/test
plugins: instafail-0.4.1.post0, forked-1.0.2, asyncio-0.15.1, variables-1.9.0, emoji-0.2.0, tavern-1.12.2, sugar-0.9.4, timeout-1.3.3, xdist-2.3.0, dependency-0.5.1, mock-3.6.1, base-url-1.4.1, html-2.1.1, django-3.7.0, cov-2.7.1, nameko-2.13.0, repeat-0.9.1, selenium-2.0.1, trio-0.7.0, Faker-4.14.0, allure-pytest-2.8.11, metadata-1.8.0, rerunfailures-10.0
collecting ... 請輸入郵箱名
collected 3 items

test_sina_login.py::TestSinaLogin::test_login_email_format PASSED
test_sina_login.py::TestSinaLogin::test_login_null PASSED
test_sina_login.py::TestSinaLogin::test_login_username_password_error PASSED

=============================== warnings summary ===============================
../../../../../Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/eventlet/patcher.py:1
  /Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/eventlet/patcher.py:1: DeprecationWarning: the imp module is deprecated in favour of importlib; see the module's documentation for alternative uses
    import imp

../../../../../Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/dns/hash.py:25
  /Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/dns/hash.py:25: DeprecationWarning: dns.hash module will be removed in future versions. Please use hashlib instead.
    DeprecationWarning)

../../../../../Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/dns/namedict.py:35
  /Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/dns/namedict.py:35: DeprecationWarning: Using or importing the ABCs from 'collections' instead of from 'collections.abc' is deprecated, and in 3.8 it will stop working
    class NameDict(collections.MutableMapping):

-- Docs: https://docs.pytest.org/en/stable/warnings.html
======================== 3 passed, 3 warnings in 16.89s ========================
[uiSeven] $ /Users/liwangping/.jenkins/tools/ru.yandex.qatools.allure.jenkins.tools.AllureCommandlineInstallation/Allure/bin/allure generate /Users/liwangping/.jenkins/workspace/uiSeven/report -c -o /Users/liwangping/.jenkins/workspace/uiSeven/allure-report
Report successfully generated to /Users/liwangping/.jenkins/workspace/uiSeven/allure-report
Allure report was successfully generated.
Creating artifact for the build.
Artifact was added to the build.
Finished: SUCCESS

點擊Allure Report的圖標,顯示的是測試報告信息,具體如下所示:

    至此,一個完整的測試框架完成,可以完整的應用於企業的實際案例中。

    感謝您的閱讀和關注!想參加服務端測試開發的同學可以加我的微信私聊,在進行招生中

 

           


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM