5-3.PageObject+Unitest

问题思考

前面我们都是基于线性模型来编写测试脚本,而且元素定位方式和属性值都是写死的。在业务场景简单的情况下这样写无可厚非,但是一旦遇到产品需求变更,业务逻辑比较复杂需要维护的时候就非常麻烦了,那么该如何应对这种情况呢?

场景案例

结合前面我们所学,测试考研帮App登录场景,按照线性模型来构造出脚本如下: 考研帮登录测试场景

kyb_login.py

from appium import webdriver

import yaml

from selenium.common.exceptions import NoSuchElementException

import logging

import logging.config

 

 

CON_LOG='../log/log.conf'

logging.config.fileConfig(CON_LOG)

logging=logging.getLogger()

 

 

stream=open('../yaml/desired_caps.yaml','r')

data=yaml.load(stream)

 

desired_caps={}

desired_caps['platformName']=data['platformName']

 

desired_caps['platformVersion']=data['platformVersion']

desired_caps['deviceName']=data['deviceName']

 

desired_caps['app']=data['app']

desired_caps['noReset']=data['noReset']

 

desired_caps['unicodeKeyboard']=data['unicodeKeyboard']

desired_caps['resetKeyboard']=data['resetKeyboard']

 

desired_caps['appPackage']=data['appPackage']

desired_caps['appActivity']=data['appActivity']

 

driver = webdriver.Remote('http://'+str(data['ip'])+':'+str(data['port'])+'/wd/hub', desired_caps)

 

def check_updateBtn():

     logging.info("check_updateBtn")

 

    try:

        element = driver.find_element_by_id('android:id/button2')

    except NoSuchElementException:

        logging.info('update element is not found!')

    else:

        element.click()

 

 

def check_skipBtn():

    logging.info("check_skipBtn")

    try:

        element = driver.find_element_by_id('com.tal.kaoyan:id/tv_skip')

    except NoSuchElementException:

        logging.info('skipBtn element is not found!')

    else:

        element.click()

 

check_updateBtn()

check_skipBtn()

 

logging.info('start login...')

 

driver.find_element_by_id('com.tal.kaoyan:id/login_email_edittext').send_keys('自学网2018')

driver.find_element_by_id('com.tal.kaoyan:id/login_password_edittext').send_keys('zxw2018')

driver.find_element_by_id('com.tal.kaoyan:id/login_login_btn').click()

logging.info('login finished')

 

 

案例分析

上面的脚本看似都比较完善,有了log采集,参数配置、启动时页面元素自动检测。但是也存在一些不足之处:

  • 公共模块和业务模块混合在一起显得代码冗余等
  • 测试场景单一(如果要实现如下测试场景该怎么办?)
  • 元素定位属性和代码混杂在一起

以上这些都是需要优化的地方。

测试场景

操作步骤

预期结果

多账号登录

不同的用户名密码来进行登录

能够正常登录

异常登录

用户名或者密码错误、或者为空进行登录,

登录失败,同时界面要给出相应的提示

注册

点击注册,然后进行注册信息填写

能够注册成功

重构优化思路

  • 将一些公共的内容(如:check_updateBtn,check_skipBtn,capability)抽离出来。
  • 元素定位方法和元素属性值与业务代码分离
  • 登录功能模块封装为一个独立的模块
  • 使用unittest进行用例综合管理

Page Object

Page Object是Selenium自动化测试项目开发实践的最佳设计模式之一,通过对界面元素的封装减少冗余代码,同时在后期维护中,若元素定位发生变化,只需要调整页面元素封装的代码,提高测试用例的可维护性。

脚本实现

封装App启动配置信息 desired_caps.py

import yaml

import logging.config

from appium import webdriver

 

 

CON_LOG = '../log/log.conf'

logging.config.fileConfig(CON_LOG)

logging = logging.getLogger()

 

 

def appium_desired():

 

    stream = open('../yaml/desired_caps.yaml', 'r')

    data = yaml.load(stream)

 

    desired_caps={}

    desired_caps['platformName']=data['platformName']

 

    desired_caps['platformVersion']=data['platformVersion']

    desired_caps['deviceName']=data['deviceName']

 

    desired_caps['app']=data['app']

    desired_caps['noReset']=data['noReset']

 

    desired_caps['unicodeKeyboard']=data['unicodeKeyboard']

    desired_caps['resetKeyboard']=data['resetKeyboard']

 

    desired_caps['appPackage']=data['appPackage']

    desired_caps['appActivity']=data['appActivity']

 

    logging.info('start run app...')

    driver = webdriver.Remote('http://'+str(data['ip'])+':'+str(data['port'])+'/wd/hub', desired_caps)

 

    driver.implicitly_wait(8)

    return driver

 

if __name__ == '__main__':

    appium_desired()

 

记得在原来的yaml配置表desired_caps.yaml补充如下内容:

unicodeKeyboard: True 

resetKeyboard: True

封装基类: baseView.py

 

class BaseView(object):

    def __init__(self,driver):

        self.driver=driver

 

    def find_element(self,*loc):

        return self.driver.find_element(*loc)

补充资料:

Python类与对象:https://static.app.yinxiang.com/embedded-web/profile/#/join?guid=1c48e955-80ea-412a-9d61-e4a45fd793ad&channel=copylink&shardId=s45&ownerId=30131358

Selenium PageObject :https://static.app.yinxiang.com/embedded-web/profile/#/join?guid=d7a9d05c-186e-4407-ba14-45dda71011f8&channel=copylink&shardId=s45&ownerId=30131358

Python可变数量参数:

封装通用公共类 common_fun.py

from appium_advance.page_object.baseView import BaseView

from selenium.common.exceptions import NoSuchElementException

import logging

from selenium.webdriver.common.by import By

from appium_advance.page_object.desired_caps import appium_desired

 

class Common(BaseView):

 

    cancelBtn=(By.ID,'android:id/button2')

    skipBtn=(By.ID,'com.tal.kaoyan:id/tv_skip')

 

    def check_cancelBtn(self):

        logging.info("============check_cancelBtn===============")

 

        try:

            element = self.driver.find_element(*self.cancelBtn)

        except NoSuchElementException:

            logging.info('update element is not found!')

        else:

            logging.info('click cancelBtn')

            element.click()

 

    def check_skipBtn(self):

        logging.info("==========check_skipBtn===========")

        try:

            element = self.driver.find_element(*self.skipBtn)

        except NoSuchElementException:

            logging.info('skipBtn element is not found!')

        else:

            logging.info('click skipBtn')

            element.click()

 

if __name__ == '__main__':

 

    driver=appium_desired()

    com=Common(driver)

    com.check_updateBtn()

    com.check_skipBtn()

 

补充资料:By方式元素定位视频讲解

封装登录操作 loginView.py

import logging

from  appium_advance.page_object.common_fun import Common

from  appium_advance.page_object.desired_caps import appium_desired

from selenium.webdriver.common.by import By

 

class LoginView(Common):

 

    username_type=(By.ID,'com.tal.kaoyan:id/login_email_edittext')

    password_type=(By.ID,'com.tal.kaoyan:id/login_password_edittext')

    loginBtn=(By.ID,'com.tal.kaoyan:id/login_login_btn')

 

 

    def login_action(self,username,password):

        self.check_cancelBtn()

        self.check_skipBtn()

 

        logging.info('===============login===============')

        logging.info('input username:%s'%username)

        self.driver.find_element(*self.username_type).send_keys(username)

 

        logging.info('input password:%s'%password)

        self.driver.find_element(*self.password_type).send_keys(password)

 

        logging.info('click loginBtn.')

        self.driver.find_element(*self.loginBtn).click()

        logging.info('login finished ')

 

if __name__ == '__main__':

    driver=appium_desired()

    l=LoginView(driver)

    l.login_action('自学网2018','zxw2018')

unittest用例封装

测试场景

使用如下账号进行分别登录测试

用户名

密码

自学网2018

zxw2018

自学网2017

zxw2017

666

222

Tips必备基础知识:Selenium自动化第六章-unittest单元测试框架

1.封装用例启动结束时的配置: myunit.py

import unittest

from appium_advance.page_object.desired_caps import appium_desired

import logging

from  time import sleep

 

class StartEnd(unittest.TestCase):

 

    def setUp(self):

        logging.info('======setUp=========')

        self.driver=appium_desired()

 

 

    def tearDown(self):

        logging.info('======tearDown=====')

        sleep(5)

        self.driver.close_app()

 

 

2.用例封装 test_login.py

from  appium_advance.unittest.myunit import StartEnd

from appium_advance.page_object.loginView import LoginView

import unittest

import logging

 

class TestLogin(StartEnd):

 

 

    def test_login_zxw2018(self):

        logging.info('=========test_login_zxw2018============')

        l=LoginView(self.driver)

        l.login_action('自学网2018','zxw2018')

 

 

    def test_login_zxw2017(self):

        logging.info('==========test_login_zxw2017========')

        l=LoginView(self.driver)

        l.login_action('自学网2017','zxw2017')

 

 

    def test_login_error(self):

        logging.info('=======test_login_error=========')

        l=LoginView(self.driver)

        l.login_action('666','222')

 

 

if __name__ == '__main__':

    unittest.main()

 

小结

 

 

 

 

代码写死一时爽,框架重构火葬场。此处功能将来必改,不要写死!

 

 

 

posted @ 2020-12-04 11:19  新客登录  阅读(94)  评论(0)    收藏  举报