บทที่ 8: Best Practices และ Project Structure¶
ก่อนอ่านบทนี้ ลองตอบ:¶
log.htmlกับreport.htmlต่างกันอย่างไร? ใช้อันไหนทำอะไร?- ถ้า test fail ด้วย "Element not visible" จะ debug อย่างไร?
ลองตอบก่อน แล้วค่อยอ่านต่อ
วัตถุประสงค์¶
อ่านจบแล้วคุณจะ: - จัดโครงสร้างโปรเจค RF ที่ maintainable ในทีมได้ - ใช้ Page Object Pattern เพื่อลด duplication - รู้ว่า code smell ของ RF มีอะไรบ้าง และแก้ยังไง - Integrate RF เข้า CI/CD pipeline เบื้องต้นได้
ทำไมต้องรู้? (Why)¶
test ที่ทำงานได้วันนี้อาจ "ทนทาน" แค่ไหน?
เดือนหน้า: - หน้า web redesign → locator ทั้งหมดเปลี่ยน - Feature ใหม่ → test case เพิ่มขึ้น 3 เท่า - สมาชิกทีมใหม่ → ต้องเข้าใจโค้ดเร็วที่สุด
Best practices คือสิ่งที่ทำให้ test โปรเจคอยู่รอดในระยะยาว ไม่ใช่แค่วันนี้
Analogy: Project Structure เหมือน บ้านที่ออกแบบดี¶
บ้านที่ดีมีห้องแยกชัดเจน — ห้องนอน, ครัว, ห้องน้ำ ไม่ปะปนกัน
RF project ที่ดีก็เหมือนกัน:
- tests/ = ห้องนอน — เก็บสิ่งที่ต้อง test (test case)
- resources/ = ครัว — เก็บเครื่องมือ (keywords)
- data/ = ห้องเก็บของ — เก็บ test data
⚠️ ถ้าเชื่อ analogy นี้ 100% จะเข้าใจผิดว่า:
- แค่แยกโฟลเดอร์ถูกก็พอ — จริงๆ structure ภายในโฟลเดอร์ก็สำคัญ เช่น แยก resource file ตาม feature ไม่ใช่ dump ทุกอย่างใน common.resource
โครงสร้างโปรเจคมาตรฐาน¶
my-qa-project/
├── tests/
│ ├── smoke/
│ │ ├── login_smoke.robot
│ │ └── homepage_smoke.robot
│ ├── regression/
│ │ ├── login_tests.robot
│ │ ├── checkout_tests.robot
│ │ └── profile_tests.robot
│ └── api/
│ └── user_api_tests.robot
│
├── resources/
│ ├── common.resource ← browser setup/teardown, screenshot
│ ├── login.resource ← login workflow keywords
│ ├── navigation.resource ← เนวิเกต menu, pages
│ └── pages/ ← Page Object files
│ ├── login_page.resource
│ ├── dashboard_page.resource
│ └── checkout_page.resource
│
├── data/
│ ├── users.yaml ← test users ทุก environment
│ ├── products.csv ← product data
│ └── config/
│ ├── staging.yaml
│ └── production.yaml
│
├── .venv/ ← ไม่ commit
├── requirements.txt ← commit
├── .gitignore
└── robot.toml ← RF config (optional)
Page Object Pattern¶
Page Object คือ resource file ที่ represent "หน้าเดียว" ของ app
ทุก action และ verification บนหน้านั้น อยู่ใน file เดียว
ตัวอย่าง Login Page Object¶
# resources/pages/login_page.resource
*** Settings ***
Library Browser
*** Variables ***
${LOGIN_URL} /login
${USERNAME_INPUT} id:username
${PASSWORD_INPUT} id:password
${LOGIN_BUTTON} css:button[type='submit']
${SUCCESS_MSG} css:.flash.success
${ERROR_MSG} css:.flash.error
*** Keywords ***
Open Login Page
[Documentation] เปิดหน้า login
Go To ${BASE_URL}${LOGIN_URL}
Login Page Should Be Open
Login Page Should Be Open
[Documentation] ตรวจสอบว่าอยู่หน้า login
Wait For Elements State ${USERNAME_INPUT} visible
Login With
[Documentation] Login ด้วย credentials ที่กำหนด
[Arguments] ${username} ${password}
Fill Text ${USERNAME_INPUT} ${username}
Fill Text ${PASSWORD_INPUT} ${password}
Click ${LOGIN_BUTTON}
Login Should Succeed
[Documentation] ตรวจสอบว่า login สำเร็จ
Wait For Elements State ${SUCCESS_MSG} visible
Login Should Fail With
[Documentation] ตรวจสอบ error message เมื่อ login ล้มเหลว
[Arguments] ${expected_message}
Wait For Elements State ${ERROR_MSG} visible
Get Text ${ERROR_MSG} contains ${expected_message}
Test file ที่ใช้ Page Object¶
# tests/regression/login_tests.robot
*** Settings ***
Library Browser
Resource resources/common.resource
Resource resources/pages/login_page.resource
Suite Setup Open Browser Session
Suite Teardown Close Browser Session
*** Variables ***
&{VALID_USER} username=tomsmith password=SuperSecretPassword!
*** Test Cases ***
Valid Login Redirects To Secure Area
[Tags] login smoke
Open Login Page
Login With ${VALID_USER}[username] ${VALID_USER}[password]
Login Should Succeed
Invalid Password Shows Error
[Tags] login negative
Open Login Page
Login With ${VALID_USER}[username] wrongpassword
Login Should Fail With Your password is invalid!
ประโยชน์ของ Page Object:
- ถ้า locator เปลี่ยน แก้แค่ login_page.resource ที่เดียว
- test file อ่านง่าย เห็นแค่ business logic
- keyword ชัดเจน reuse ได้
Code Smells ที่พบบ่อย¶
1. Magic Numbers/Strings¶
# ❌ ไม่รู้ว่า 300 มาจากไหน
Should Be Equal As Numbers ${total} 300
# ✅ ชัดเจน มีความหมาย
${EXPECTED_TOTAL} 300
Should Be Equal As Numbers ${total} ${EXPECTED_TOTAL}
2. God Keyword (ทำทุกอย่างใน keyword เดียว)¶
# ❌ keyword ยาวเกิน ทำหลายอย่าง
Complete Purchase
Open Cart
Apply Discount Code SAVE10
Fill Shipping Address ... # 5 fields
Fill Payment Info ... # 4 fields
Confirm Order
Verify Order Confirmation
Check Email Notification
# ✅ แบ่งเป็น keyword ย่อย
Complete Purchase
Open Cart
Apply Discount Code SAVE10
Fill Checkout Details
Confirm And Verify Order
Fill Checkout Details
Fill Shipping Address
Fill Payment Info
Confirm And Verify Order
Confirm Order
Verify Order Confirmation
Check Email Notification
3. Test Case ที่ขึ้นกันกัน (Test Dependencies)¶
# ❌ test 2 ต้องรันหลัง test 1 เท่านั้น
Test 1 - Login
Login As admin pass123
Test 2 - Go To Profile
# สมมติว่า login อยู่แล้วจาก test 1
Go To Profile Page # ถ้า test 1 fail → test 2 fail ด้วยทั้งที่ logic ถูก
# ✅ แต่ละ test อิสระกัน
Test 2 - Go To Profile
[Setup] Login As admin pass123 # login เองใน test
Go To Profile Page
การ Integrate กับ CI/CD¶
GitHub Actions (ตัวอย่าง)¶
# .github/workflows/robot-tests.yml
name: Robot Framework Tests
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.12'
- name: Install dependencies
run: |
pip install -r requirements.txt
rfbrowser init
- name: Run Robot Framework tests
run: |
robot --include smoke \
--output results/output.xml \
--log results/log.html \
--report results/report.html \
tests/
- name: Upload test results
uses: actions/upload-artifact@v3
if: always() # upload แม้ test fail
with:
name: robot-results
path: results/
คำสั่ง RF ที่ CI/CD ใช้บ่อย¶
# รัน smoke test, exit code 1 ถ้า fail
robot --include smoke tests/
# รัน ทุก test แต่ไม่ fail build ถ้า acceptance test fail
robot --include smoke tests/ && echo "Smoke passed"
# รัน แบบ parallel (ต้องติดตั้ง pabot)
pip install robotframework-pabot
pabot --processes 4 tests/
ตัวอย่าง 3 ระดับ¶
Beginner — เขียน test ที่เป็นอิสระจากกัน¶
# tested: Robot Framework 7.4.2, Python 3.14.2
*** Settings ***
Library Collections
*** Test Cases ***
Calculate Shipping For Domestic Order
[Documentation] Test shipping calculation สำหรับออเดอร์ในประเทศ
[Tags] shipping smoke
[Setup] Initialize Shipping Rules
${fee}= Calculate Shipping Fee 500 domestic
Should Be Equal As Numbers ${fee} 50
Calculate Shipping For Heavy Domestic Order
[Documentation] Test shipping calculation สำหรับออเดอร์หนักในประเทศ
[Tags] shipping regression
[Setup] Initialize Shipping Rules
${fee}= Calculate Shipping Fee 3000 domestic
Should Be Equal As Numbers ${fee} 100
Calculate Shipping For International Order
[Documentation] Test shipping calculation สำหรับออเดอร์ต่างประเทศ
[Tags] shipping regression
[Setup] Initialize Shipping Rules
${fee}= Calculate Shipping Fee 500 international
Should Be Equal As Numbers ${fee} 350
*** Keywords ***
Initialize Shipping Rules
${rules}= Create Dictionary
... domestic_standard=50
... domestic_heavy=100
... international_standard=350
Set Test Variable ${SHIPPING_RULES} ${rules}
Calculate Shipping Fee
[Arguments] ${order_value} ${destination}
IF '${destination}' == 'domestic'
IF ${order_value} >= 2000
RETURN ${SHIPPING_RULES}[domestic_heavy]
ELSE
RETURN ${SHIPPING_RULES}[domestic_standard]
END
ELSE
RETURN ${SHIPPING_RULES}[international_standard]
END
Output ที่ควรได้:
Calculate Shipping For Domestic Order | PASS |
Calculate Shipping For Heavy Domestic Order | PASS |
Calculate Shipping For International Order | PASS |
3 tests, 3 passed, 0 failed
Intermediate — Page Object + ใช้ข้อมูลจาก Dictionary¶
# tested: Robot Framework 7.4.2, Python 3.14.2
# จำลอง Page Object pattern โดยไม่ต้องมี browser
*** Settings ***
Library Collections
*** Variables ***
# Simulated Page State (จำลองแทน browser)
&{PAGE_STATE} is_logged_in=${False} current_user=${EMPTY} page=login
*** Test Cases ***
Complete User Journey From Login To Profile Update
[Documentation] ทดสอบ user journey: login → view profile → update name
[Tags] user-journey regression
Login Page Should Be Showing
User Logs In With alice pass123
Dashboard Should Be Showing alice
User Navigates To Profile
User Updates Display Name Alice Smith
Profile Name Should Be Updated To Alice Smith
*** Keywords ***
Login Page Should Be Showing
Should Be Equal ${PAGE_STATE}[page] login
User Logs In With
[Arguments] ${username} ${password}
Should Not Be Empty ${username}
Should Not Be Empty ${password}
Set To Dictionary ${PAGE_STATE} is_logged_in=${True}
Set To Dictionary ${PAGE_STATE} current_user=${username}
Set To Dictionary ${PAGE_STATE} page=dashboard
Dashboard Should Be Showing
[Arguments] ${expected_user}
Should Be True ${PAGE_STATE}[is_logged_in]
Should Be Equal ${PAGE_STATE}[current_user] ${expected_user}
Should Be Equal ${PAGE_STATE}[page] dashboard
User Navigates To Profile
Set To Dictionary ${PAGE_STATE} page=profile
User Updates Display Name
[Arguments] ${new_name}
Set To Dictionary ${PAGE_STATE} display_name=${new_name}
Profile Name Should Be Updated To
[Arguments] ${expected_name}
Dictionary Should Contain Key ${PAGE_STATE} display_name
Should Be Equal ${PAGE_STATE}[display_name] ${expected_name}
Advanced — Config-driven test ที่รองรับหลาย environment¶
# tested: Robot Framework 7.4.2, Python 3.14.2
*** Settings ***
Library Collections
Library OperatingSystem
*** Variables ***
${ENV} staging # เปลี่ยนได้ด้วย --variable ENV:production
&{STAGING_CONFIG} base_url=https://staging.example.com api_key=stg-key-123 timeout=30
&{PROD_CONFIG} base_url=https://example.com api_key=prod-key-456 timeout=10
*** Test Cases ***
API Config Should Match Environment
[Documentation] ตรวจสอบว่า config ถูก load ตาม environment
[Tags] config smoke
${config}= Get Config For Environment ${ENV}
Should Not Be Empty ${config}[base_url]
Should Not Be Empty ${config}[api_key]
Should Be True ${config}[timeout] > 0
Log Environment: ${ENV}
Log Base URL: ${config}[base_url]
Log Timeout: ${config}[timeout]s
Staging Config Should Have Longer Timeout
[Documentation] Staging ควรมี timeout ยาวกว่า production
[Tags] config regression
${staging}= Get Config For Environment staging
${prod}= Get Config For Environment production
Should Be True ${staging}[timeout] > ${prod}[timeout]
Log Staging timeout: ${staging}[timeout]s > Prod timeout: ${prod}[timeout]s
*** Keywords ***
Get Config For Environment
[Documentation] คืน config dictionary ตาม environment ที่กำหนด
[Arguments] ${environment}
IF '${environment}' == 'staging'
RETURN ${STAGING_CONFIG}
ELSE IF '${environment}' == 'production'
RETURN ${PROD_CONFIG}
ELSE
Fail Unknown environment: ${environment}
END
Output ที่ควรได้:
API Config Should Match Environment | PASS |
Staging Config Should Have Longer Timeout | PASS |
2 tests, 2 passed, 0 failed
Checklist ก่อน Merge Test Code¶
ใช้ checklist นี้ review test code ก่อน push:
- [ ] Test case มี
[Documentation]อธิบายว่า test อะไร - [ ] Test case มี
[Tags]อย่างน้อย 1 tag - [ ] แต่ละ test case เป็นอิสระ ไม่ขึ้นกับ test อื่น
- [ ] Locator เก็บเป็น Variable ไม่ hardcode ใน keyword
- [ ] ไม่มี
Sleep— ใช้Wait For Elements Stateแทน - [ ] Browser ปิดใน
[Teardown]ไม่ใช่ท้าย test case - [ ] Test data ที่ใช้ซ้ำอยู่ใน Variable หรือ external file
- [ ] Keyword ชื่อสื่อความหมาย อ่านแล้วรู้ว่าทำอะไร
- [ ] Resource file แยกตาม feature (login, checkout, etc.)
Common Mistakes¶
❌ ทุก test ขึ้นกับ state จาก test ก่อนหน้า
# ผิด — ถ้า Test 1 fail, Test 2 อาจ pass หรือ fail ไม่แน่นอน
Test 1 - Add Item
Add To Cart laptop
Test 2 - Verify Cart
Cart Should Have Item laptop # ขึ้นอยู่กับ Test 1 ผ่านหรือเปล่า?
# ถูก — แต่ละ test manage state ของตัวเอง
Test 2 - Verify Cart
[Setup] Add To Cart laptop
Cart Should Have Item laptop
❌ ไม่มี CI/CD integration — รัน test แค่บน local machine
# ผิด — ไม่มีหลักฐานว่า test ผ่านบน server จริง
"Test ผ่านในเครื่องฉันนะ"
# ถูก — ทุก PR trigger test อัตโนมัติ
GitHub Actions / GitLab CI / Jenkins → run tests → report
❌ ตั้งชื่อ test case ไม่สื่อความหมาย
# ผิด — ไม่รู้ว่า test อะไร หรือ expect อะไร
Test001
Login As admin pass123
# ถูก — บอก scenario และ expected outcome
Admin Login With Valid Credentials Should Show Dashboard
Login As admin pass123
Dashboard Should Be Visible
สรุป Course¶
คุณเรียนจบ Robot Framework สำหรับ QA แล้ว! มาสรุปสิ่งที่เรียนมา:
| บท | สิ่งที่ได้ |
|---|---|
| 1 | ทำไม RF, keyword-driven testing, Browser Library vs Selenium |
| 2 | ติดตั้ง Python + RF + Browser Library, first test |
| 3 | 4 Sections, Variables 3 แบบ, Spacing rules |
| 4 | Keywords, Arguments, Resource Files, Page Object |
| 5 | Locators, Browser actions, Wait vs Sleep |
| 6 | Test Template, Data-Driven, FOR loop |
| 7 | Report.html, Log.html, Debug flow |
| 8 | Project structure, Page Object, CI/CD |
ขั้นต่อไปที่แนะนำ: 1. ทำ exercise ใน exercises.md ให้ครบ 2. ลอง setup โปรเจคจริงกับ app ที่ทีมคุณกำลัง test อยู่ 3. เรียนเพิ่มเติม: RequestsLibrary สำหรับ API testing 4. เรียนเพิ่มเติม: AppiumLibrary สำหรับ mobile testing