## 目錄
* 簡介
* Gherkin language 是什麼?
* 會使用套件與版本
* 建立簡單測試專案
1. 建立 python 虛擬環境
2. 安裝相關套件
3. 撰寫測試腳本
4. 撰寫測試程式集
* E2E
1. selenium
2. pytest fixture
3. 建立簡單E2E測試
4. 專案目錄
5. 執行測試專案
* 參考資料
### 簡介
> 使用 pytest-bdd 搭配測試, 測試腳本是使用 Gherkin language。
### Gherkin language 是什麼?
> Gherkin 是使用自然語言(英文、中文)對操作行為與使用情境進行描述的簡單結構語法。
``` Gherkin
Feature: Login functionality 測試功能
Scenario: Login to system 情境描述
Given I visit login page 前置條件
When I enter account and password 當事件發生
And I press the login button
Then I should see home page 結果
```
### 會使用套件與版本
* python 3.9.0
* pytest 7.4.3
* pytest-bdd 7.0.1
* selenium 4.19.0
### 建立簡單測試專案
1. 建立專案資料夾
```
mkdir tests
cd tests
mkdir features functional
```
> 目錄結構
```
測試專案結構
tests
│
└──features
│
│
└──functional
```
#### 建立 python 虛擬環境
1. 進入專案內
> 目前位置為 /tests
2. 建立 python 虛擬環境
```
python3 -m venv test_venv(名稱)
```
> 目錄會出現 test_venv 資料夾
3. 啟動 python 虛擬環境
```
source test_venv/bin/activate
```
4. 環境啟動成功在終端機的指令列前端會出現 (虛擬名稱)。
5. 停用虛擬環境指令
```
deactivate
```
#### 安裝相關套件
位置: 虛擬環境內的 /tests 目錄下
1. 安裝 pytest
```
pip install pytest
```
2. 安裝 pytest
```
pip install pytest-bdd
```
4. 檢查套件是否安裝成功
```
python -m pip list
```
> 會出現套件列表
```
Package Version
------------------ -----------
pytest 7.4.3
pytest-bdd 7.0.1
```
#### 撰寫測試腳本
位置: tests/features 目錄下
1. 新增 login.feature 檔案。
2. 在 login.feature 內使用 Gherkin language 規範進行撰寫測試腳本。
``` Gherkin
Feature: Login functionality
Scenario: Login to system
Given I visit login page
When I enter account and password
And I press the login button
Then I should see home page
```
#### 撰寫測試程式集
位置: tests/features 目錄下
1. 新增 test_login 檔案。
``` python
import pytest
from pytest_bdd import scenarios, given, when, then
scenarios('login.feature')
@given('I visit login page ')
def login():
pass
@when('I enter account and password')
def set_account_password():
pass
@when('I press the login button')
def click_button():
pass
@then(parsers.parse('I should see the "{title}" page'))
def login_success(driver, title):
assert 1 == 1
```
2. 執行測試
```
python3 -m pytest
```
### E2E
> E2E 是 End-to-end testing(端對端測試)的簡寫
> 使用 pytest-Bdd 搭配 selenium 進行網站的 E2E
#### selenium
> selenium 是自動化操作瀏覽器的工具,常用於測試與爬蟲。
> 安裝 selenium
```
pip install selenium
```
#### pytest fixture
* fixture 是 pytest 特有的功能,是在測試程式準備階段被呼叫的函式,可用於處理環境設定,如資料庫連線、測試資料集等。fixture 函式會以依賴註入方式帶入測試函式內應用。
* 在特定情境,fixture 會使用 yield 傳送值,如瀏覽器物件的傳遞,為了能正確的關閉。
* yield 惰性求值(lazy evaluation) 的特性,在生成資料傳遞之後會回到函式的特性,來實現 xUnit-style setup/teardown 功能,在一個函式進行建立與關閉。
#### 建立簡單E2E測試
> 以登入測試為例
[完整範例程式](https://github.com/qnsak/example_pytest_bdd/tree/master)
#### 專案目錄
```
tests
│
└──features
│ │
│ └──login.feature
└──functional
│
└──test_login.py
```
1. login.feature
``` feature
Feature: Login functionality
Scenario: Login to system
Given'I visit login page
When I enter user in the account field
And I enter 123456 in the password field
And I press the login button
Then I should see the hello
```
2. test_login.py
> 在 pytest-bdd 導入 selenium
``` python
import pytest
from pytest_bdd import scenarios, given, when, then
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.ui import WebDriverWait
@pytest.fixture
def driver():
# 取得瀏覽器的設定資料
browser_options = Options()
# 不顯示瀏覽器
browser_options.add_argument('--headless')
# 启动浏览器
browser = webdriver.Chrome(options=browser_options)
# 等待瀏覽器開啟
browser.implicitly_wait(20)
# 送出瀏覽器物件
yield browser
# 关闭浏览器
browser.quit()
scenarios('login.feature')
@given(parsers.parse('I visit login page'))
def set_account(driver, path):
driver.get('http://127.0.0.1:3005/'+path)
@when(parsers.parse('I enter {account} in the account field'))
def set_password(driver, account):
driver.find_element(By.NAME, 'name').send_keys(account)
@when(parsers.parse('I enter {password} in the password field'))
def set_password(driver, password):
driver.find_element(By.NAME, 'password').send_keys(password)
@when(parsers.parse('I press the login button'))
def click_login_button(driver):
driver.find_element(By.NAME, 'login_button').click()
@then(parsers.parse('I should see the hello'))
def login_success(driver, title):
data = WebDriverWait(driver,100).until(EC.presence_of_element_located((By.XPATH, '/html/body/h1')))
assert 'hello' == data.text
```
#### 執行測試專案
1. 安裝測試套件
``` shell
# 進入測試專案,建議使用 python 虛擬環境
cd tests
# 安裝套件
pip install -r requirements.txt
```
2. 安裝簡單登入網頁
``` shell
# 進入測試專案,建議使用 python 虛擬環境
cd web
# 安裝套件
pip install -r requirements.txt
```
3. 執行測試
``` shell
# 到測試專案目錄執行測試
pytest
```
## 參考資料
[PYTHON TESTING 101: PYTEST-BDD](https://automationpanda.com/2018/10/22/python-testing-101-pytest-bdd/)
[Pytest-BDD’s documentation!](https://pytest-bdd.readthedocs.io/en/stable/)
[pypi](https://pypi.org/project/pytest-bdd/)
[pytest-with-eric](https://pytest-with-eric.com/bdd/pytest-bdd/)
[從 0 開始培育成為自動化測試工程師的學習指南系列 第 23 篇](https://ithelp.ithome.com.tw/articles/10326971?sc=rss.iron)
[pytest fixture](https://blog.tzing.tw/posts/python-testing-pytest-fixture-91b547f2)
[automationpanda](https://automationpanda.com/2023/01/12/passing-test-inputs-into-pytest/)
[ithome - 關於Gherkin](https://ithelp.ithome.com.tw/articles/10226615)