[TOC]
## 重構頁面類
### PO(Page Objects)設計模式
PO設計模式,旨在為每個待測網頁創建一個對象。通過這樣的設計,**將業務邏輯代碼與測試代碼分離** 。其中業務邏輯都封裝在頁面類中。
根據這個設計思路,我們可將頁面登錄過程定義在登錄頁面類方法中。
在測試代碼中,直接引用登錄頁面類中的登錄方法,即可完成登錄操作。
編寫一個`page.py` 模塊,其中定義一個基類與登錄頁面類
```python
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from selenium.webdriver.common.by import By
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
class BasePage(object):
""" 定義所有頁面類的基類 """
def __init__(self, driver):
self.driver = driver
def get_source(self):
"""
獲取頁面源碼
:return:
"""
return self.driver.page_source
def accept_alert(self):
"""
切換到alert窗口,并且點擊確定
:return: 返回alert信息
"""
wait = WebDriverWait(self.driver, 10, 0.5)
wait.until(EC.alert_is_present())
alert = self.driver.switch_to.alert
return alert
class LoginPage(BasePage):
""" 登錄頁面類 """
# 定義第一個待測頁面
url = "http://a.4399en.com/"
def login(self, user, pwd):
# 打開首頁
self.driver.get(LoginPage.url)
# 點擊右上角登錄按鈕打開登錄窗口
self.driver.find_element_by_class_name("CNlogin").click()
# 輸入用戶名
wait = WebDriverWait(self.driver, 20, 0.5)
account = wait.until(EC.visibility_of_element_located((By.ID, "modify-account")))
account.clear()
account.send_keys(user)
# 輸入密碼
password = self.driver.find_element_by_id("modify-password")
password.clear()
password.send_keys(pwd)
# 點擊登錄按鈕
self.driver.find_element_by_xpath("//input[@type='submit'][@value='Login']").click()
if __name__ == '__main__':
from selenium import webdriver
options = webdriver.ChromeOptions()
options.add_argument('User-Agent=Mozilla/5.0 (Linux; U; Android 4.0.2; en-us; Galaxy Nexus Build/ICL53F) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30')
driver = webdriver.Chrome(chrome_options=options)
login_page = LoginPage(driver)
login_page.login("gfc@qq.com", "123456")
```
主要關注`BasePage`和`LoginPage`這兩個類的代碼。` if __name__ == '__main__'` 中的代碼不是非必要的,主要是調試當前頁面類的代碼,不會在其他模塊調用時運行。
## 重構測試代碼
### 使用pytest測試框架來組織用例
>[warning]如果你還不懂得如何使用pytest測試框架,請先閱讀我的另外一本python入門書中的測試框架《[接口測試框架](http://www.hmoore.net/guanfuchang/python_start/703690)》
下面直接演示我的例子:
首先,定義公共的fixture,新建一個fixture文件`conftest.py`:
```python
#!/usr/bin/python
#coding=utf-8
from selenium import webdriver
import pytest
@pytest.fixture()
def get_driver():
# 創建Chrome驅動實例,這里創建driver時,傳入chrome_options參數,告訴服務器,我是用移動端瀏覽器訪問的。
options = webdriver.ChromeOptions()
options.add_argument('User-Agent=Mozilla/5.0 (Linux; U; Android 4.0.2; en-us; Galaxy Nexus Build/ICL53F) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30')
driver = webdriver.Chrome(chrome_options=options)
# 設置瀏覽器大小,讓它看起來跟手機的樣式差不多。
driver.set_window_size("380", "680")
# 設置一個全局的等待超時時間 10s
driver.implicitly_wait(10)
yield driver
driver.quit()
```
我為什么會將fixture抽離出來,放到公共文件中呢?那是因為考慮到一個項目,后續會有很多測試類,會在各個測試類中共用fixture。
下面以前面的登錄測試類為例,新建一個測試文件`test_login.py`:
```python
#!/usr/bin/python
#coding=utf-8
import pytest
from page import LoginPage
class TestLogin(object):
def test_login_succss(self, get_driver):
"""登錄成功用例"""
login_page = LoginPage(get_driver)
login_page.login("gfc@qq.com","123456")
assert "gfc@qq.com" in login_page.get_source()
def test_login_fail(self, get_driver):
"""登錄失敗用例"""
login_page = LoginPage(get_driver)
login_page.login("gfc@123.com","123456")
alert = login_page.accept_alert()
assert "User does not exist!" in alert.text
if __name__ == '__main__':
pytest.main()
```
運行效果如

## 思考優化點
* 元素定位與頁面類分離
* 測試數據與測試代碼分離
* 輸出優美的測試報告
很多書籍會建議我們將元素定位與頁面分離,但是以我初學者的角度,我認為這一步不用那么著急著做,直接定義在頁面類中,在開發和調試時,,會更加便捷。
繼續關注下一章,測試代碼與Allure結合,生成優美的測試報告。
<hr style="margin-top:50px">
<section class="" style="box-sizing: border-box;" powered-by="xiumi.us"><section class="" style="margin: 40px 0% 10px;box-sizing: border-box;"><section class="" style="display: inline-block;width: 100%;border-width: 5px;border-style: double;border-color: rgb(23, 22, 24);padding: 10px;border-radius: 2px;box-sizing: border-box;"><section class="" style="box-sizing: border-box;" powered-by="xiumi.us"><section class="" style="transform: translate3d(20px, 0px, 0px);-webkit-transform: translate3d(20px, 0px, 0px);-moz-transform: translate3d(20px, 0px, 0px);-o-transform: translate3d(20px, 0px, 0px);font-size: 11px;margin: -50px 0% 0px;box-sizing: border-box;"><section class="" style="box-sizing: border-box;width: 7em;height: 7em;display: inline-block;vertical-align: bottom;border-radius: 100%;border-width: 4px;border-style: double;border-color: rgb(23, 22, 24);background-position: center center;background-repeat: no-repeat;background-size: cover;background-image: url("http://pav7h2emv.bkt.clouddn.com/FnD-fHkNDLN1-b02XmnMvsz6ld-n");"><section class="" style="width: 100%;height: 100%;overflow: hidden;box-sizing: border-box;"><img class="" data-ratio="0.6012024" data-w="499" data-src="http://pav7h2emv.bkt.clouddn.com/FnD-fHkNDLN1-b02XmnMvsz6ld-n" style="opacity: 0; box-sizing: border-box; width: 100% !important; height: auto !important; visibility: visible !important;" width="100%" data-type="jpeg" _width="100%" src="http://pav7h2emv.bkt.clouddn.com/FnD-fHkNDLN1-b02XmnMvsz6ld-n" data-fail="0"></section></section></section></section><section class="" style="box-sizing: border-box;" powered-by="xiumi.us"><section class="" style="margin: -30px 0% 30px;box-sizing: border-box;"><section class="" style="display: inline-block;vertical-align: top;width: 61.8%;padding: 0px 15px;box-sizing: border-box;"><section class="" style="box-sizing: border-box;" powered-by="xiumi.us"><section class="" style="margin: 40px 0% 0px;box-sizing: border-box;"><section class="" style="color: rgb(160, 160, 160);box-sizing: border-box;"><p style="margin: 0px;padding: 0px;box-sizing: border-box;">微信公眾號:</p><p style="margin: 0px;padding: 0px;box-sizing: border-box;">python測試開發圈</p><p style="margin: 0px;padding: 0px;box-sizing: border-box;"><br style="box-sizing: border-box;"></p></section></section></section></section><section class="" style="display: inline-block;vertical-align: top;width: 38.2%;box-sizing: border-box;"><section class="" style="box-sizing: border-box;" powered-by="xiumi.us"><section class="" style="text-align: center;margin: 10px 0% 0px;box-sizing: border-box;"><section class="" style="max-width: 100%;vertical-align: middle;display: inline-block;border-width: 0px;border-radius: 0px;box-shadow: rgb(0, 0, 0) 0px 0px 0px;width: 90%;overflow: hidden !important;box-sizing: border-box;"><img data-ratio="1" data-w="430" data-src="http://pav7h2emv.bkt.clouddn.com/FibGgIJSMfHtehzeWOOzjdQKSMx5" style="vertical-align: middle; max-width: 100%; box-sizing: border-box; width: 100% !important; height: auto !important; visibility: visible !important;" width="100%" data-type="jpeg" _width="100%" class="" src="http://pav7h2emv.bkt.clouddn.com/FibGgIJSMfHtehzeWOOzjdQKSMx5" data-fail="0"></section></section></section></section></section></section><section class="" style="box-sizing: border-box;" powered-by="xiumi.us"><section class="" style="margin: -30px 0% 0px;box-sizing: border-box;"><section class="" style="display: inline-block;vertical-align: top;width: 61.8%;padding: 0px 15px;box-sizing: border-box;"><section class="" style="box-sizing: border-box;" powered-by="xiumi.us"><section class="" style="transform: translate3d(5px, 0px, 0px);-webkit-transform: translate3d(5px, 0px, 0px);-moz-transform: translate3d(5px, 0px, 0px);-o-transform: translate3d(5px, 0px, 0px);box-sizing: border-box;"><section class="" style="color: rgb(160, 160, 160);font-size: 14px;box-sizing: border-box;"><p style="margin: 0px;padding: 0px;box-sizing: border-box;">一起分享學習與成長路線</p></section></section></section></section><section class="" style="display: inline-block;vertical-align: top;width: 38.2%;box-sizing: border-box;"><section class="" style="box-sizing: border-box;" powered-by="xiumi.us"><section class="" style="transform: translate3d(10px, 0px, 0px);-webkit-transform: translate3d(10px, 0px, 0px);-moz-transform: translate3d(10px, 0px, 0px);-o-transform: translate3d(10px, 0px, 0px);box-sizing: border-box;"><section class="" style="color: rgb(160, 160, 160);font-size: 14px;box-sizing: border-box;"><p style="margin: 0px;padding: 0px;box-sizing: border-box;">長按(或掃描)二維碼關注</p></section></section></section></section></section></section></section></section></section>