
運行環境: pip insall pytest pytest-bdd pytest-selenium
BDD介紹
BDD行為驅動是一種敏捷開發模式, 重點在於消除開發/測試對需求了解的歧義及用戶場景的驗證.
需求描述/用戶場景
BDD提供一套標准的需求及用戶場景表達語法, 一般為Feature(需求), Scenario(場景), Given(假設,預置條件), When(操作步驟), Then(驗證及清理), 如下為一個需求描述(有的公司稱為需求卡片):
文件名:
educa.feature
需求使用專門的.feature
作為后綴
Feature: educa在線課程網站需求
需求描述: 提供后台添加課程及課程內容, 前台學生瀏覽課程, 加入課程后可查看課程詳情
Scenario: 通過educa后台添加課程
Given 用戶:hanzhichao, 密碼:hanzhichao123
And 分類:接口測試,標題:Python接口測試教程,描述:作者,臨淵
When 登錄educa后台
And 點擊:Courses模塊->點擊新增按鈕
And 作者選擇當前<用戶>,選擇<分類>,輸入<標題>,<描述>,點擊保存
Then 頁面中應存在名稱為<標題>的鏈接
And 刪除該課程
#Scenario: 學生選課
# ...
- 一個需求文件中只能有一個Feature字段, 可以包含多個Scenario(用戶場景)
Given->When->Then
類似與准備->執行->驗證/清理
的流程- Given: 一般可以用來做預置條件/數據准備, 下面第一個And也屬於Given
- When下面的量And都屬於When, 一般是操作步驟, <用戶>等只是用來提醒使用的是Given中的數據, 也可以不使用<>
- Then: 一般用於驗證結果(斷言), 也可以進行清理數據
場景解析/實現
單有場景文件是不能執行的, 在BDD的初級使用中, 測試同學還需要將每個場景文件中的描述翻譯成具體的頁面操作, 每一句對應一個函數, 下面是使用pytest-bdd對上訴educt.feature
的解析實現:
# file_name: scenario_steps.py
from pytest_bdd import given, when, then, parsers
from selenium.webdriver.support.select import Select
from selenium.webdriver.support import expected_conditions as EC
@given(parsers.parse("用戶:{username}, 密碼:{password}"))
def user(username, password): # 類似一個pytest的fixture方法, 其他步驟可以使用其返回值
return dict(username=username, password=password)
@given(parsers.parse("分類:{category},標題:{title},描述:{description}"))
def course(category, title, description):
return dict(category=category, title=title, description=description)
@when("登錄educa后台") # 固定操作,不需要獲取參數則不用parsers.parse()
def login(selenium, user): # 使用上面user函數的返回數據, selenium為瀏覽器driver(來着:pytest-selenium)
selenium.get("http://qaschool.cn:8000/admin/")
selenium.find_element_by_id("id_username").send_keys(user['username'])
selenium.find_element_by_id("id_password").send_keys(user['password'])
selenium.find_element_by_class_name("submit-row").click()
@when(parsers.parse("點擊:{module}模塊->點擊新增按鈕"))
def add_course(selenium, module):
selenium.find_element_by_link_text(module).click() # 點擊'Courses'鏈接
selenium.find_element_by_class_name("addlink").click() # 點擊'新增 COURSE'按鈕
@when("作者選擇當前<用戶>,選擇<分類>,輸入<標題>,<描述>,點擊保存") # 也可以不使用<>, 要與場景中一致, 使用<>只是提示是從Given的數據中獲取
def edit_course(selenium, user, course): # 使用上面course函數的返回數據
Select(selenium.find_element_by_id("id_owner")).select_by_visible_text(user['username']) # 選擇作者
Select(selenium.find_element_by_id("id_subject")).select_by_visible_text(course['category']) # 選擇主題
selenium.find_element_by_id("id_title").send_keys(course['title']) # 輸入文章標題
selenium.find_element_by_id("id_overview").send_keys(course['description']) # 輸入描述
selenium.find_element_by_class_name("default").click() # 點擊保存
@then("頁面中應存在名稱為<標題>的鏈接")
def check_course(course):
assert EC.presence_of_element_located(("link text", course['title']))
@then("刪除該課程")
def delete_course(selenium, course):
selenium.find_element_by_link_text(course['title']).click()
selenium.find_element_by_class_name("deletelink").click()
selenium.find_element_by_css_selector("input[type='submit']").click()
parsers
用於解析語句中的參數- 方法中的
selenium
參數為使用pytest-selenium中的瀏覽器driver, 固定參數名 EC.presence_of_element_located
用來驗證可定位到元素
場景測試
# file_name: test_educa.py
from pytest_bdd import scenario
from scenario_steps import * # 導入場景解釋/支持步驟
@scenario("educa.feature", "通過educa后台添加課程")
def test_add_course(): # 測試educa需求文件中名為"通過educa后台添加課程"的場景
pass # 可以不寫內容, pass即可
- 場景測試也可以和場景實現寫到一起
執行測試
使用pytest-selenium執行用例是需要指定瀏覽器
在test_educa.py所在目錄命令行中執行:
pytest test_educa.py --driver Chrome
Pytest-bdd的參數化
待補充...
注: 上文提到BDD的初級使用,是因為這是一種被動的測試模式, 每一個不同的需求卡片的每一句都需要去進行解釋實現, 其中有大量的重復性工作, 另外缺乏開發的參與與支持.
除了部分程度上, 消除測試同學需求理解的歧義性及讓測試同學更注重用戶場景的驗證而不是開發(功能點)邏輯的驗證外, 這基本上跟寫selenium自動化腳本一樣, 由於場景解釋腳本的不穩定而耗費大量的工作卻無法發現有價值的問題
BDD行為驅動的最佳實踐,請見下回分解...