不懂PO 设计模式?这篇实战文带你搞定 PO

49 阅读7分钟

为UI页面写测试用例时(比如web页面,移动端页面),测试用例会存在大量元素和操作细节。当UI变化时,测试用例也要跟着变化, PageObject 很好的解决了这个问题!

使用UI自动化测试工具时(包括selenium,appium等),如果无统一模式进行规范,随着用例的增多会变得难以维护,而 PageObject 让自动化脚本井井有序,将 page 单独维护并封装细节,可以使 testcase 更稳健,不需要大改大动。

具体做法:把元素信息和操作细节封装到Page类中,在测试用例上调用Page对象(PageObject),比如存在一个功能“选取相册标题”,需要为之建立函数selectAblumWithTitle(),函数内部是操作细节findElementsWithClass('album')等: 以选“取相册标题”举例,伪代码如下:

selectAblumWithTitle() {
    #选取相册
        findElementsWithClass('album')
            #选取相册标题
                findElementsWithClass('title-field')
                    #返回标题内容
                        return getText()
                        
                        }
                        
                        ```
                        
                        
                        page object的主要原则是提供一个简单接口 (或者函数,比如上述的selectAblumWithTitle),让调用者在页面上可以做任何操作,点击页面元素,在输入框输入内容等。
                        
                        因此,如果要访问一个文本字段,page object应该有获取和返回字符串的方法。page object应该封装对数据的操作细节,比如查找元素和点击元素。当页面元素改动时,应该只改变page类中的内容,不需要改变调用它的地方。
                        
                        不要为每个UI页面都创建一个page类,应该只为页面中重要的元素创建page类。
                        
                        比如,一个页面显示多个相册,应该创建一个相册列表page object,它包含许多相册page object。如果某些复杂UI的层次结构只是用来组织UI,那么它就不应该出现在page object中。page object的目的是通过给页面建模,从而对应用程序的使用者变得有意义:
                        ![](https://ceshiren.com/uploads/default/original/3X/f/6/f61e0d3c205cf16473503161bac8a694400426b5.png)
                        
                        
                        如果你想导航到另一个页面,初始page对象应当return另一个page对象,比如点击注册,进入注册页面,在代码中就应该return Register()。如果想获取页面信息,可以return基本类型(字符串、日期)。
                        
                        建议不要在page object中放断言。应该去测page object,而不是让page object自己测自己,page object的责任是提供页面的状态信息。这里仅用HTML描述Page Object,这种模式还可以用来隐藏Java swing UI细节,它可用于所有UI框架。
                        
                        
                        ## 
                        PageObject的核心思想是六大原则,掌握六大原则才可以进行 PageObject 实战演练,这是 PageObject的精髓所在。
                        selenium官方凝聚出六大原则,后面的PageObject使用都将围绕六大原则开展:
                        - 公共方法代表页面提供的服务
                        - 不要暴露页面细节
                        - 不要把断言和操作细节混用
                        - 方法可以return到新打开的页面
                        - 不要把整页内容都放到PO中
                        - 相同的行为会产生不同的结果,可以封装不同结果
                        
                        下面,对上述六大原则进行解释:
                        - 原则一:要封装页面中的功能(或者服务),比如点击页面中的元素,可以进入到新的页面,于是,可以为这个服务封装方法“进入新页面”。
                        - 原则二:封装细节,对外只提供方法名(或者接口)。
                        - 原则三:封装的操作细节中不要使用断言,把断言放到单独的模块中,比如testcase。
                        - 原则四:点击一个按钮会开启新的页面,可以用return方法表示跳转,比如return MainPage()表示跳转到新的PO:MainPage。
                        - 原则五:只为页面中重要的元素进行PO设计,舍弃不重要的内容。
                        - 原则六:一个动作可能产生不同结果,比如点击按钮后,可能点击成功,也可能点击失败,为两种结果封装两个方法,click_success和click_error。
                        
                        
                        ## 
                        
                        以企业微信首页为例,企业微信首页有二个主要功能:立即注册和企业登录。
                        企业微信网址:https://work.weixin.qq.com/
                        
                        
                        
                        ![](https://ceshiren.com/uploads/default/original/3X/0/d/0d5c9e2f5e1f6bd7bbc133681e83a78de726a837.png)
                        点击企业登录可以进入登录页面,在页面可以扫码登录和企业注册。
                        
                        
                        ![](https://ceshiren.com/uploads/default/original/3X/3/f/3f1f441981dae1ce6e4358124ba2bc910ecac959.png)
                        点击企业注册可以进入注册页面,在页面可以输入相关信息进行注册。
                        
                        
                        
                        ![](https://ceshiren.com/uploads/default/original/3X/5/1/51770439705bcd68f1e6c0475fb6a110192cf825.png)
                        
                        
                        用page object原则为页面建模,这里涉及三个页面:首页,登录,注册。在代码中创建对应的三个类Index,Login,Register:
                        •  登陆页⾯提供login findPassword功能
                        –          Login类 + login findPassword⽅法
                        •  登录页⾯内的元素有多少并不关⼼,隐藏内部界⾯控件
                        •  登录成功和失败会分别返回不同的页⾯
                        –          findPassword
                        –          loginSuccess
                        –          loginFail
                        •  通过⽅法返回值判断登录是否符合预期
                        
                        
                        
                        ![](https://ceshiren.com/uploads/default/original/3X/3/f/3fc082a5ad52ed3617bad3ff875c44a5a3d3836a.png)
                        
                        
                        ![](https://ceshiren.com/uploads/default/original/3X/d/a/daf0ec695d3c4af03d10d81f348395958d902e46.png)
                        
                        
                        BasePage是所有page object的父类,它为子类提供公共的方法,比如下面的BasePage提供初始化driver和退出driver,代码中在base_page模块的BasePage类中使用__init__初始方法进行初始化操作,包括driver的复用,driver的赋值,全局等待的设置(隐式等待)等等:
                        
                        ```
                        from time import sleep
                        from selenium import webdriver
                        from selenium.webdriver.remote.webdriver import WebDriver
                        
                        
                        class BasePage:
                            def __init__(self, driver: WebDriver = None):
                                    #此处对driver进行复用,如果不存在driver,就构造一个新的
                                            if driver is None:
                                                        # Index页面需要用,首次使用时构造新driver
                                                                    self._driver = webdriver.Chrome()
                                                                                # 设置隐式等待时间
                                                                                            self._driver.implicitly_wait(3)
                                                                                                        # 访问网页
                                                                                                                    self._driver.get(self._base_url)
                                                                                                                            else:
                                                                                                                                        # Login与Register等页面需要用这个方法,避免重复构造driver
                                                                                                                                                    self._driver = driver
                                                                                                                                                    
                                                                                                                                                        def close(self):
                                                                                                                                                                sleep(20)
                                                                                                                                                                        self._driver.quit()
                                                                                                                                                                        
                                                                                                                                                                        
                                                                                                                                                                        ```
                                                                                                                                                                        
                                                                                                                                                                        
                                                                                                                                                                        
                                                                                                                                                                        Index是企业微信首页的page object,它存在两个方法,进入注册page object和进入登录page object,这里return方法返回page object实现了页面跳转,比如:goto_register方法return Register,实现从首页跳转到注册页:
                                                                                                                                                                        
                                                                                                                                                                        ```
                                                                                                                                                                        class Index(BasePage):
                                                                                                                                                                            _base_url = "https://work.weixin.qq.com/"
                                                                                                                                                                                # 进入注册页面
                                                                                                                                                                                    def goto_register(self):
                                                                                                                                                                                            self._driver.find_element(By.LINK_TEXT, "立即注册").click()
                                                                                                                                                                                                    # 创建Register实例后,可调用Register中的方法
                                                                                                                                                                                                            return Register(self._driver)
                                                                                                                                                                                                                # 进入登录页面
                                                                                                                                                                                                                    def goto_login(self):
                                                                                                                                                                                                                            self._driver.find_element(By.LINK_TEXT, "企业登录").click()
                                                                                                                                                                                                                                    # 创建Login实例后,可调用Login中的方法
                                                                                                                                                                                                                                            return Login(self._driver)
                                                                                                                                                                                                                                            
                                                                                                                                                                                                                                            
                                                                                                                                                                                                                                            ```
                                                                                                                                                                                                                                            
                                                                                                                                                                                                                                            
                                                                                                                                                                                                                                            
                                                                                                                                                                                                                                            Login是登录页面的page object,主要功能有:进入注册页面,扫描二维码,因此创建两个方法代表两个功能:scan_qrcode和goto_registry。代码跟上面相似,不过多介绍:
                                                                                                                                                                                                                                            
                                                                                                                                                                                                                                            ```
                                                                                                                                                                                                                                            from selenium.webdriver.common.by import By
                                                                                                                                                                                                                                            from test_selenium.page.base_page import BasePage
                                                                                                                                                                                                                                            from test_selenium.page.register import Register
                                                                                                                                                                                                                                            
                                                                                                                                                                                                                                            class Login(BasePage):
                                                                                                                                                                                                                                                # 扫描二维码
                                                                                                                                                                                                                                                    def scan_qrcode(self):
                                                                                                                                                                                                                                                            pass
                                                                                                                                                                                                                                                                # 进入注册页面
                                                                                                                                                                                                                                                                    def goto_registry(self):
                                                                                                                                                                                                                                                                            self._driver.find_element(By.LINK_TEXT, "企业注册").click()
                                                                                                                                                                                                                                                                                    return Register(self._driver)
                                                                                                                                                                                                                                                                                    
                                                                                                                                                                                                                                                                                    
                                                                                                                                                                                                                                                                                    ```
                                                                                                                                                                                                                                                                                    
                                                                                                                                                                                                                                                                                    
                                                                                                                                                                                                                                                                                    
                                                                                                                                                                                                                                                                                    Register是注册页面的page object,主要功能是填写正确注册信息,当填写错误时,返回错误信息。register方法实现了正确的表格填写,当填写完毕时返回自身(页面还停留在注册页)。get_error_message方法实现了错误填写的情况,如果填写错误,就收集错误内容并返回:
                                                                                                                                                                                                                                                                                    
                                                                                                                                                                                                                                                                                    ```
                                                                                                                                                                                                                                                                                    from selenium.webdriver.common.by import By
                                                                                                                                                                                                                                                                                    from test_selenium.page.base_page import BasePage
                                                                                                                                                                                                                                                                                    
                                                                                                                                                                                                                                                                                    
                                                                                                                                                                                                                                                                                    class Register(BasePage):
                                                                                                                                                                                                                                                                                        # 填写注册信息,此处只填写了部分信息,并没有填写完全
                                                                                                                                                                                                                                                                                            def register(self, corpname):
                                                                                                                                                                                                                                                                                                    # 进行表格填写
                                                                                                                                                                                                                                                                                                            self._driver.find_element(By.ID, "corp_name").send_keys(corpname)
                                                                                                                                                                                                                                                                                                                    self._driver.find_element(By.ID, "submit_btn").click()
                                                                                                                                                                                                                                                                                                                            # 填写完毕,停留在注册页,可继续调用Register内的方法 
                                                                                                                                                                                                                                                                                                                                    return self
                                                                                                                                                                                                                                                                                                                                        #填写错误时,返回错误信息
                                                                                                                                                                                                                                                                                                                                            def get_error_message(self):
                                                                                                                                                                                                                                                                                                                                                    # 收集错误信息并返回
                                                                                                                                                                                                                                                                                                                                                            result=[]
                                                                                                                                                                                                                                                                                                                                                                    for element in self._driver.find_elements(By.CSS_SELECTOR, ".js_error_msg"):
                                                                                                                                                                                                                                                                                                                                                                                result.append(element.text)
                                                                                                                                                                                                                                                                                                                                                                                
                                                                                                                                                                                                                                                                                                                                                                                        return result
                                                                                                                                                                                                                                                                                                                                                                                        
                                                                                                                                                                                                                                                                                                                                                                                        
                                                                                                                                                                                                                                                                                                                                                                                        ```
                                                                                                                                                                                                                                                                                                                                                                                        
                                                                                                                                                                                                                                                                                                                                                                                        
                                                                                                                                                                                                                                                                                                                                                                                        
                                                                                                                                                                                                                                                                                                                                                                                        test_index模块是对上述功能的测试,它独立于page类,在TestIndex类中只需要调用page类提供的方法即可,比如下面对注册页及登陆页的测试使用了test_register和test_login方法:
                                                                                                                                                                                                                                                                                                                                                                                        ```
                                                                                                                                                                                                                                                                                                                                                                                        from test_selenium.page.index import Index
                                                                                                                                                                                                                                                                                                                                                                                        
                                                                                                                                                                                                                                                                                                                                                                                        
                                                                                                                                                                                                                                                                                                                                                                                        class TestIndex:
                                                                                                                                                                                                                                                                                                                                                                                            # 所有步骤前的初始化
                                                                                                                                                                                                                                                                                                                                                                                                def setup(self):
                                                                                                                                                                                                                                                                                                                                                                                                        self.index = Index()
                                                                                                                                                                                                                                                                                                                                                                                                            # 对注册功能的测试
                                                                                                                                                                                                                                                                                                                                                                                                                def test_register(self):
                                                                                                                                                                                                                                                                                                                                                                                                                        # 进入index,然后进入注册页填写信息
                                                                                                                                                                                                                                                                                                                                                                                                                                self.index.goto_register().register("霍格沃兹测试学院")
                                                                                                                                                                                                                                                                                                                                                                                                                                    # 对login功能的测试
                                                                                                                                                                                                                                                                                                                                                                                                                                        def test_login(self):
                                                                                                                                                                                                                                                                                                                                                                                                                                                # 从首页进入到注册页
                                                                                                                                                                                                                                                                                                                                                                                                                                                        register_page = self.index.goto_login().goto_registry()\
                                                                                                                                                                                                                                                                                                                                                                                                                                                                    .register("测吧(北京)科技有限公司")
                                                                                                                                                                                                                                                                                                                                                                                                                                                                            # 对填写结果进行断言,是否填写成功或者填写失败
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    assert "请选择" in "|".join(register_page.get_error_message())
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        # 关闭driver
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            def teardown(self):
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    self.index.close()
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    ```
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    内容全面升级,5 个月 20+ 项目实战强化训练,资深测试架构师、开源项目作者亲授 BAT 大厂前沿最佳实践,带你一站式掌握测试开发必备核心技能(对标阿里P6+,年薪50W+)!直推 BAT 名企测试经理,普遍涨薪 50%+!
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    ⬇️ 点击“阅读原文”,提升测试核心竞争力!
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    [原文链接](https://mp.weixin.qq.com/s?__biz=MzU3NDM4ODEzMg==&mid=2247492911&idx=1&sn=53b392c2cd1bf3c50655aad3316bf614&chksm=fd3187e4ca460ef210ec4d1a2178c4d91a0990ee6de84f436610d0dae717aeda4580ee7e0065#rd) 
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    [更多技术文章](https://qrcode.ceba.ceshiren.com/link?name=article&project_id=qrcode&from=juejin&timestamp=1663233229&author=ML)