QA Automate · Knowledge Base
เรียนรู้ Automated Testing
ให้ได้ผลจริง
ระบบที่ทดสอบด้วยมือทุกครั้งก่อน Deploy เสียเวลา เสียงบ และยังพลาด Bug — นี่คือปัญหาที่ Automated Testing ถูกสร้างมาเพื่อแก้
ควรรู้อะไรก่อนเริ่มเรียน Automate?
Automated Testing ไม่ใช่จุดเริ่มต้น — แต่คือขั้นถัดไปของคนที่มีพื้นฐานเหล่านี้มาแล้ว รู้ก่อนยิ่งเรียนได้เร็วขึ้นมาก
Title : ปุ่ม Login ไม่ตอบสนอง เมื่อ Email ว่างเปล่า Priority : High | Environment : Chrome 124 / Staging Steps to Reproduce: 1. เปิดหน้า /login 2. ปล่อย Email ว่าง ใส่ Password ถูกต้อง 3. คลิก "เข้าสู่ระบบ" Expected : แสดง validation "กรุณากรอก Email" Actual : ปุ่มไม่ตอบสนอง ไม่มี message ใดปรากฏ ไม่มี network call
# Variable & Data Type name = "สมชาย" age = 25 is_admin = True # Condition if age >= 18: print("ผู้ใหญ่") else: print("เด็ก") # Loop for i in range(3): print(f"รอบที่ {i+1}") # Function def get_login_payload(email, password): return {"email": email, "password": password}
"1" กับ 1 ต่างกัน — Assert ผิด Type ทำให้ Test ผ่านทั้งที่ Bug ซ่อนอยู่
รู้จัก Date/Time format, XML, CSV เบื้องต้น
{
"user": {
"id": 42, ← Number (ไม่ใช่ String)
"name": "สมชาย",
"is_admin": true, ← Boolean
"score": 98.5,
"address": null, ← Null ≠ "" (empty string)
"tags": ["admin", "qa"] ← Array
},
"created_at": "2026-04-27T10:30:00Z" ← ISO 8601
}
⚠ Type ต่างกัน → Assert ให้ผลต่าง
"100" ≠ 100 # String vs Number
null ≠ "" # Null vs Empty String
false ≠ "false" # Boolean vs String
── Request ────────────────────────────────────────── POST /api/auth/login HTTP/1.1 Host: api.example.com Content-Type: application/json Authorization: Bearer eyJhbGciOiJIUzI1NiJ9... { "email": "user@test.com", "password": "secret" } ── Response ───────────────────────────────────────── HTTP/1.1 200 OK Content-Type: application/json { "token": "abc123", "status": "success" } ── Status Code ที่ต้อง Test ────────────────────────── 200 OK 401 Unauthorized 404 Not Found 201 Created 403 Forbidden 500 Server Error 400 Bad Request 422 Unprocessable
# ติดตั้งและรัน Robot Framework pip install robotframework pip install robotframework-seleniumlibrary robot --outputdir results tests/ # Git — จัดการ Test Script git init git add tests/login_test.robot git commit -m "add: login test cases" git push origin main # ดู log และ diff git log --oneline git diff HEAD~1 tests/
── User Story ─────────────────────────────────────── As a registered user I want to login with email and password So that I can access my dashboard Acceptance Criteria: ✅ แสดง dashboard เมื่อ credential ถูกต้อง ✅ แสดง error เมื่อ password ผิด ✅ Lock account หลัง fail 5 ครั้ง ── แปลงเป็น Test Cases ────────────────────────────── ควร Login สำเร็จเมื่อ Credential ถูกต้อง ควรแสดง Error เมื่อ Password ผิด ควร Lock Account หลัง Login ผิด 5 ครั้งติดกัน
ประเภทของ Automated Test
แต่ละประเภทมีจุดประสงค์ต่างกัน — เข้าใจความแตกต่างช่วยให้เลือกใช้ได้ถูกที่ และจัดสัดส่วน Test Suite ได้อย่างมีประสิทธิภาพตาม Testing Pyramid
Unit Test
ทดสอบหน่วยย่อยที่สุดของ Code เช่น Function หรือ Method เดียว แยกออกจากระบบภายนอกด้วย Mock/Stub ให้ผลลัพธ์รวดเร็วและชี้จุด Bug ได้แม่นยำ
Integration Test
ทดสอบว่า Component หลายตัวทำงานร่วมกันได้ถูกต้อง เช่น Service + Database หรือ API + External Service ตรวจจับปัญหาที่ Unit Test มองไม่เห็น
End-to-End Test
จำลอง User Journey จริงตั้งแต่ต้นจนจบผ่าน UI ทดสอบว่าระบบทั้งหมดทำงานร่วมกัน ได้ถูกต้องในสภาพแวดล้อมที่เหมือน Production
API Test
ทดสอบ REST/GraphQL API โดยตรง ไม่ผ่าน UI ตรวจสอบ Status Code, Response Body, Headers และ Business Logic ที่ Logic Layer
Mobile App Test
Automate การทดสอบบน iOS และ Android ทั้ง Native, Hybrid และ Web App ด้วย AppiumLibrary ที่ Robot Framework รองรับ
Event / Message Test
ทดสอบระบบ Event-Driven เช่น Kafka, RabbitMQ ตรวจสอบว่า Event ถูก Publish และ Consume ได้ถูกต้อง รองรับ Async ด้วย Wait Until
| ชั้น Pyramid | สัดส่วน | ความเร็ว | ค่าใช้จ่าย | Robot Framework Library |
|---|---|---|---|---|
| Unit Test (ฐาน) | ~70% | ms | ต่ำ | Built-in / Custom Keywords |
| Integration Test | ~20% | 1–10s | ปานกลาง | RequestsLibrary, DatabaseLibrary |
| E2E Test (ยอด) | ~10% | นาที | สูง | Browser Library, SeleniumLibrary |
ทุก Framework ใช้โครงสร้างเดียวกัน
ไม่ว่าจะเป็น Robot Framework, Jest, Pytest หรือ JUnit — ทุก Test ล้วนมี 4 Phase เหมือนกัน ต่างกันเพียง Syntax เท่านั้น เมื่อเข้าใจ Pattern นี้ การเรียน Framework ใหม่ใช้เวลาน้อยลงมาก
PreStep
เตรียมสภาพแวดล้อม ข้อมูล และ Dependencies ก่อน Test จะทำงาน เทียบได้กับ Arrange ใน AAA Pattern
[Setup] / Suite SetupMain Execute
ดำเนินการ Action ที่ต้องการทดสอบจริง เรียก Function, API หรือ Interact กับ UI เทียบได้กับ Act ใน AAA Pattern
Test Case bodyVerify Result
เปรียบเทียบผลลัพธ์จริงกับสิ่งที่คาดหวัง ถ้าไม่ตรงกัน Test จะ Fail เทียบได้กับ Assert ใน AAA Pattern
Should Be Equal / Should ContainPostStep
ทำความสะอาดและคืนสภาพแวดล้อมสู่สถานะเดิม ลบ Test Data ปิด Connection ทำงานแม้ Test จะ Fail
[Teardown] / Suite Teardown| Phase | Robot Framework | Jest / Vitest | Pytest | JUnit 5 |
|---|---|---|---|---|
| PreStep | [Setup] / Suite Setup | beforeEach / beforeAll | @pytest.fixture | @BeforeEach / @BeforeAll |
| Main Execute | Test Case body | it() / test() body | def test_*() body | @Test method body |
| Verify Result | Should Be Equal / Should Contain | expect().toBe() / toEqual() | assert / pytest.approx | Assertions.assertEquals() |
| PostStep | [Teardown] / Suite Teardown | afterEach / afterAll | yield fixture | @AfterEach / @AfterAll |
Robot Framework Ecosystem
Robot Framework เป็น Keyword-Driven Framework ที่ขยายความสามารถด้วย Library ทำให้ Automate ได้ทุกอย่างตั้งแต่ Web, API, Mobile จนถึง Message Queue
Robot Framework
Framework หลักที่ใช้ Keyword-Driven Syntax อ่านเข้าใจได้แม้ไม่ใช่นักพัฒนา รองรับทุก Test Type ผ่าน Library และมี Ecosystem กว้างขวาง
✦ แนะนำสำหรับ QA ทุกระดับBrowser Library
RF Library สำหรับ Web Automation ที่ใช้ Playwright เป็น Engine มี Auto-Wait ในตัว รองรับ Chrome, Firefox, Safari และ Trace Viewer
✦ แนะนำสำหรับ Web E2E ใหม่RequestsLibrary
Library สำหรับทดสอบ REST API และ HTTP Requests ครอบคลุม GET/POST/PUT/DELETE พร้อม Assert Response Status, Body และ Headers
✦ แนะนำสำหรับ API TestingAppiumLibrary
Library สำหรับ Automate Mobile App บน iOS และ Android รองรับทั้ง Native App, Hybrid App และ Mobile Web ผ่าน Appium Server
✦ แนะนำสำหรับ Mobile TestingKafkaLibrary
Custom Library สำหรับทดสอบ Kafka Event Streaming ตรวจสอบ Publish, Consume และ Message Content ด้วย Wait Until Keyword สำหรับ Async Flow
✦ สำหรับ Event-Driven ArchitectureDatabaseLibrary
Library สำหรับเชื่อมต่อและ Query Database โดยตรง ใช้ Verify ว่าข้อมูล ถูก Insert/Update ถูกต้องหลัง API Call หรือ Business Flow ทำงานครบ
✦ สำหรับ Integration & E2E Verification| ความต้องการ | Library ที่แนะนำ | Test Layer |
|---|---|---|
| ทดสอบ Web UI (Modern) | Browser Library (Playwright) | E2E |
| ทดสอบ REST API | RequestsLibrary | Integration / API |
| ทดสอบ Mobile App | AppiumLibrary | E2E |
| ทดสอบ Kafka / Event | KafkaLibrary (custom) | Integration |
| Verify ข้อมูลใน DB | DatabaseLibrary | Integration |
| ทดสอบ Web UI (Legacy) | SeleniumLibrary | E2E |
เขียน Test ให้ดูแลได้ระยะยาว
Test ที่ดีไม่ใช่แค่ผ่าน — แต่ต้องอ่านรู้เรื่อง แก้ได้ง่าย และเชื่อถือได้เมื่อ Fail
ตั้งชื่อ Test ให้อ่านแล้วเข้าใจทันที
ชื่อ Test Case คือ Documentation ที่มีชีวิต — เมื่อ Test Fail ชื่อที่ดีบอกทันทีว่าเกิดอะไร ใน Robot Framework เขียนชื่อเหมือนประโยค Requirement
# ❌ ชื่อที่ไม่ดี — ไม่รู้ว่าทดสอบอะไร Test1 Login Test TC_001 # ✅ ชื่อที่ดี — อ่านแล้วรู้ทันที ควร Login สำเร็จเมื่อ Email และ Password ถูกต้อง ควรแสดง Error เมื่อ Password ผิด ควร Lock Account หลัง Login ผิด 5 ครั้งติดกัน
Test หนึ่งตัวทดสอบ Behavior เดียว
Test ที่ตรวจหลาย Behavior พร้อมกัน เมื่อ Fail จะรู้แค่ว่า "มีอะไรผิด" แต่ไม่รู้อะไร แยก Test ออกตาม Behavior ทำให้ Root Cause Analysis เร็วขึ้นและ Verify Result Phase มีความหมายชัดเจน
# ❌ แบบที่ไม่ดี — ทดสอบหลาย Behavior ในตัวเดียว # เมื่อ Fail จะรู้แค่ว่า "User Flow พัง" ไม่รู้ว่าพังตรงไหน User Flow ทั้งหมด Login user@test.com secret ${profile}= Get Profile Should Be Equal ${profile}[name] สมชาย Update Profile name=สมหญิง Logout Element Should Be Visible id=login-page # ✅ แบบที่ดี — แยกแต่ละ Behavior ออกจากกัน # Fail ที่ Test ไหน รู้ทันทีว่าพัง Behavior อะไร ควร Login สำเร็จเมื่อ Credential ถูกต้อง ... ควรแสดงชื่อ User ใน Profile หลัง Login ... ควร Redirect กลับ Login Page หลัง Logout ...
ทำให้ Test เป็นอิสระต่อกัน
PreStep ที่ดีคือหัวใจของ Test Independence — แต่ละ Test สร้างและทำความสะอาด State ของตัวเองเสมอ อย่าให้ Test B อิง State ที่ Test A ทิ้งไว้ เพราะรันเดี่ยว Test B จะ Fail ทันที
*** Settings *** Test Setup Reset And Seed Database Test Teardown Clean Up Test Data *** Keywords *** Reset And Seed Database Delete All Users Insert User email=user@test.com password=hashed123 # ✅ ทุก Test เริ่มต้นจากสถานะเดิมเสมอ # รันทีละ Test หรือรันทั้งหมดก็ได้ผลเหมือนกัน
หลีกเลี่ยง Flaky Test ด้วย Event-Driven Wait
Flaky Test เกิดจาก Main Execute Phase ที่มี Timing Dependency — แทน Sleep 3s ให้ใช้ Wait Until ที่ Framework มีให้ Framework จะรอจนเงื่อนไขพร้อมเองโดยอัตโนมัติ
# ❌ แบบที่ทำให้ Flaky — ขึ้นกับ network speed Click Element id=btn-submit Sleep 3s Element Should Be Visible id=success-msg # ✅ แบบที่ถูกต้อง — รอจน element พร้อมเอง Click Element id=btn-submit Wait Until Element Is Visible id=success-msg timeout=10s # ✅ API — รอจน status เปลี่ยน Wait Until Keyword Succeeds 10x 1s Verify Order Status COMPLETED
รัน Test ใน CI/CD ทุก Pull Request
Test ที่รันแค่บนเครื่อง Dev ไม่ให้ความมั่นใจเพียงพอ CI/CD Pipeline คือ PreStep ระดับ Organization — รันใน Environment สะอาดทุกครั้ง ตรวจจับ Regression ก่อนถึง Production
name: Robot Framework Tests on: [push, pull_request] jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Install dependencies run: pip install robotframework robotframework-requests - name: Run tests run: robot --outputdir results tests/ - name: Upload report uses: actions/upload-artifact@v4 with: name: robot-report path: results/
- ตั้งชื่อ Test เป็นประโยคอ่านรู้เรื่อง
- ใช้ Test Setup / Teardown ทำความสะอาด State
- ใช้ Wait Until แทน Sleep
- Mock External Service ที่ไม่เสถียร
- รัน Test ใน CI/CD ทุก Pull Request
- อัปเดต Test เมื่อ Requirement เปลี่ยน
- ตั้งชื่อ Test1, TestLogin, TestAll
- แชร์ State หรือ Data ระหว่าง Test
- ใช้ Sleep แบบ Hardcode
- ทดสอบหลาย Behavior ในตัวเดียว
- ปล่อย Flaky Test ค้างไว้โดยไม่แก้
- Skip Test แทนที่จะ Fix Root Cause
Robot Framework ตัวอย่างจริง
ตัวอย่าง Code ที่ใช้งานได้จริงพร้อม Comment ชี้ให้เห็น 4 Phase ในแต่ละ Test
*** Settings *** Library RequestsLibrary Library Collections *** Variables *** ${BASE_URL} https://api.example.com ${TOKEN} Bearer test-token-123 *** Test Cases *** ควร Login สำเร็จเมื่อ Credential ถูกต้อง # ── PreStep ──────────────────────────────────────── Create Session myapi ${BASE_URL} verify=True # ── Main Execute ─────────────────────────────────── ${resp}= POST On Session myapi /auth/login ... json={"email": "user@example.com", "password": "secret"} # ── Verify Result ────────────────────────────────── Should Be Equal As Integers ${resp.status_code} 200 ${body}= Set Variable ${resp.json()} Should Not Be Empty ${body}[token] Should Be Equal ${body}[status] "success" # ── PostStep ─────────────────────────────────────── Delete All Sessions ควรได้รับ 401 เมื่อ Password ผิด Create Session myapi ${BASE_URL} ${resp}= POST On Session myapi /auth/login ... json={"email": "user@example.com", "password": "wrong"} ... expected_status=401 ${body}= Set Variable ${resp.json()} Should Be Equal ${body}[error] "invalid_credentials" [Teardown] Delete All Sessions
*** Settings *** Library Browser Suite Setup Open Browser And Navigate Suite Teardown Close Browser *** Variables *** ${BASE_URL} https://app.example.com *** Test Cases *** ควร Login สำเร็จและ Redirect ไป Dashboard # ── PreStep ──────────────────────────────────────── New Page ${BASE_URL}/login # ── Main Execute ─────────────────────────────────── Fill Text id=email user@example.com Fill Text id=password secret123 Click id=login-btn # ── Verify Result ────────────────────────────────── Wait For Navigation Get Url == ${BASE_URL}/dashboard Get Text h1 == ยินดีต้อนรับ # ── PostStep ─────────────────────────────────────── [Teardown] Close Page ควรแสดง Error เมื่อ Password ผิด New Page ${BASE_URL}/login Fill Text id=email user@example.com Fill Text id=password wrong-password Click id=login-btn Wait For Elements State .error-msg visible timeout=5s Get Text .error-msg == Email หรือ Password ไม่ถูกต้อง [Teardown] Close Page *** Keywords *** Open Browser And Navigate New Browser chromium headless=False New Context viewport={width: 1280, height: 720}
*** Settings *** Library AppiumLibrary *** Variables *** ${REMOTE_URL} http://localhost:4723/wd/hub &{CAPS} ... platformName=Android ... deviceName=emulator-5554 ... appPackage=com.example.app ... appActivity=.MainActivity ... automationName=UiAutomator2 *** Test Cases *** ควรเพิ่มสินค้าลงตะกร้าได้สำเร็จ # ── PreStep ──────────────────────────────────────── Open Application ${REMOTE_URL} &{CAPS} Login To App user@example.com secret # ── Main Execute ─────────────────────────────────── Click Element id=product-001 Click Element id=add-to-cart-btn # ── Verify Result ────────────────────────────────── Wait Until Element Is Visible id=cart-badge timeout=5s Element Text Should Be id=cart-count 1 Element Should Be Visible id=cart-success-toast # ── PostStep ─────────────────────────────────────── [Teardown] Close Application *** Keywords *** Login To App [Arguments] ${email} ${password} Input Text id=email-input ${email} Input Text id=pass-input ${password} Click Element id=login-button Wait Until Element Is Visible id=home-screen timeout=10s
*** Settings *** Library KafkaLibrary Library RequestsLibrary Library Collections *** Variables *** ${KAFKA_BROKER} localhost:9092 ${API_URL} http://localhost:8080 ${TOPIC} order.created *** Test Cases *** ควร Publish order.created Event เมื่อสร้าง Order สำเร็จ # ── PreStep ──────────────────────────────────────── Connect Kafka Consumer ${KAFKA_BROKER} ${TOPIC} Create Session api ${API_URL} # ── Main Execute ─────────────────────────────────── ${resp}= POST On Session api /orders ... json={"product_id": "P001", "quantity": 2} ${order_id}= Set Variable ${resp.json()}[order_id] # ── Verify Result ────────────────────────────────── Should Be Equal As Integers ${resp.status_code} 201 ${event}= Wait Until Kafka Message topic=${TOPIC} ... key=${order_id} timeout=10s Should Be Equal ${event}[event_type] order.created Should Be Equal ${event}[product_id] P001 # ── PostStep ─────────────────────────────────────── [Teardown] Run Keywords ... Delete Order Via API ${order_id} ... AND Disconnect Kafka Consumer ... AND Delete All Sessions *** Keywords *** Delete Order Via API [Arguments] ${order_id} DELETE On Session api /orders/${order_id}
Locator Strategy
วิธีระบุ Element ที่ถูกต้องคือหัวใจของ Web & Mobile Test — เลือก Locator ที่เสถียรทำให้ Test ไม่พังเมื่อ UI เปลี่ยน
id ของ element — เร็วที่สุด เสถียรที่สุด
ควรใช้ก่อนเสมอถ้า Dev ใส่ id ไว้
# ใช้ id= prefix Click Element id=btn-login Input Text id=username admin@test.com Element Should Be Visible id=success-msg
# ใช้ css= กับ attribute selector Click Element css=[data-testid="btn-submit"] Input Text css=[data-testid="input-email"] user@test.com Element Should Be Visible css=[data-testid="error-msg"]
# by class Click Element css=button.btn-primary # by attribute Input Text css=input[type="email"] user@test.com # child combinator Click Element css=#form-login > button[type="submit"] # nth-child Click Element css=.product-list .card:nth-child(2)
# ✅ relative xpath — ค้นด้วย attribute Click Element xpath=//button[@id="btn-login"] Click Element xpath=//button[contains(text(),"Login")] # ✅ traverse — หา input ที่อยู่ถัด label Input Text xpath=//label[text()="Email"]/following-sibling::input user@test.com # ✅ nth element Click Element xpath=(//div[@class="card"])[2] # ❌ absolute xpath — อย่าใช้ # xpath=/html/body/div/main/div[3]/form/button
id ของ Web
Android ใช้ content-desc / iOS ใช้ accessibilityIdentifier
ควรขอให้ Dev ใส่ไว้เสมอ
# ใช้ได้ทั้ง iOS และ Android Click Element accessibility_id=login-button Input Text accessibility_id=email-field user@test.com Input Text accessibility_id=password-field secret123 Click Element accessibility_id=submit-btn
com.package.name:id/element_id
ดู resource-id ได้จาก Android Studio หรือ uiautomatorviewer
# Android resource-id Click Element id=com.myapp.android:id/btn_login Input Text id=com.myapp.android:id/et_email user@test.com # UiAutomator2 — ค้นด้วย text Click Element android=new UiSelector().text("เข้าสู่ระบบ") Click Element android=new UiSelector().textContains("Login")
# iOS Predicate String Click Element predicate string=label == "Login" Click Element predicate string=name == "btn_submit" Click Element predicate string=type == "XCUIElementTypeButton" AND label CONTAINS "Login" # Class Chain — traverse แบบ iOS XPath Click Element chain=**/XCUIElementTypeButton[`label == "Login"`]
# Android XPath Click Element xpath=//android.widget.Button[@text="Login"] Click Element xpath=//android.widget.EditText[@resource-id="et_email"] # iOS XPath Click Element xpath=//XCUIElementTypeButton[@label="Login"] # ❌ Absolute xpath บน Mobile ช้ามาก อย่าใช้ # xpath=//hierarchy/android.widget.FrameLayout/...
คำศัพท์ที่ควรรู้
คำศัพท์หลักใน Automated Testing, Robot Framework, Locator Strategy และ HTTP