NỘI DUNG BÀI HỌC

Chống lặp code (DRY): Hiểu rõ Fixture là gì và tại sao nó là "cỗ máy" chuẩn bị (setup) và dọn dẹp (teardown) mạnh mẽ nhất của Pytest.
Làm chủ Setup & Teardown: Sử dụng yield để tách biệt phần "chuẩn bị" (ví dụ: đăng nhập, tạo data) khỏi phần "dọn dẹp" (ví dụ: đăng xuất, xóa data) một cách an toàn.
TỐI ƯU HÓA TỐC ĐỘ: Hiểu sâu sắc 4 cấp độ scope của Fixture (function, class, module, session) để quyết định khi nào cần cô lập (isolation) và khi nào cần hiệu suất (performance).

Trong các framework cũ (như TestNG hay Unittest), chúng ta thường đau đầu với các hàm @BeforeMethod, @AfterMethod chồng chéo, copy-paste code khắp nơi. Pytest mang đến một "vũ khí" tối thượng mang tên Fixture. Nếu hiểu và làm chủ được nó, framework của bạn sẽ gọn gàng và chạy nhanh hơn gấp nhiều lần.


1. Khái niệm: Fixture là gì?

Đừng nghĩ về Fixture bằng những định nghĩa kỹ thuật khô khan. Hãy tưởng tượng việc chạy một Test Case giống như việc nấu một món ăn:

  • Setup (Chuẩn bị): Rửa chảo, bật bếp, thái thịt.

  • Test (Nấu): Bỏ đồ vào xào, nêm nếm.

  • Teardown (Dọn dẹp): Rửa bát, lau bếp.


Fixture chính là "Người phụ bếp" của bạn.
Bạn chỉ cần ra lệnh: "Cho tôi cái chảo nóng". Người phụ bếp (Fixture) sẽ tự động làm bước Setup (rửa chảo, bật bếp), đưa chảo cho bạn nấu (Test), và ngay khi bạn nấu xong, họ sẽ tự động đem đi rửa (Teardown) mà bạn không cần phải nhắc.

Trong code, Fixture chia đôi hai thế giới Setup và Teardown chỉ bằng một từ khóa cực kỳ thanh lịch: yield.

#Cú pháp
import pytest

@pytest.fixture
def nguoi_phu_bep():
    # 1. SETUP (Trước yield): Chạy TRƯỚC khi test bắt đầu
    print("Bật bếp, lấy chảo")
    chao = "Chảo đã nóng"
    
    # 2. YIELD: Giao chảo cho hàm Test sử dụng
    yield chao 
    
    # 3. TEARDOWN (Sau yield): Chạy SAU khi test kết thúc (Dù test pass hay fail)
    print("Tắt bếp, đem chảo đi rửa")

# Hàm Test chỉ việc dùng "nguoi_phu_bep"
def test_chien_trung(nguoi_phu_bep):
    chao_cua_toi = nguoi_phu_bep
    print(f"Đang chiên trứng trên {chao_cua_toi}")

 

2. Scope (Phạm vi) - Chìa khóa tối ưu tốc độ thực thi

Scope quyết định việc "Người phụ bếp" sẽ chuẩn bị đồ cho bạn tần suất như thế nào. Chọn sai Scope, hệ thống chạy rùa bò; chọn đúng, bạn tối ưu được 80% thời gian chạy.

A. scope="function" (Mặc định)

  • Tần suất: Chạy lại Setup/Teardown cho MỖI HÀM TEST.

  • Analogy (Đời sống): Một chiếc bát ăn cơm dùng một lần. Mỗi "lần ăn" (test case) bạn lại lấy một cái bát mới tinh. Ăn xong là rửa (teardown).

  • Bản chất: Đảm bảo test case này không bị "nhiễm khuẩn" (cookie, cache, data) từ test case trước.
  • Khi nào dùng trong Auto Test:

    • Đây là lựa chọn AN TOÀN và PHỔ BIẾN NHẤT.

    • Khi bạn cần sự cô lập tuyệt đối. Mỗi test case phải bắt đầu từ một trạng thái sạch sẽ, không bị ảnh hưởng bởi test case trước đó.

    • Ví dụ 1 (Playwright): Fixture page mặc định của pytest-playwright dùng scope này. Mỗi test case (def test_abc(page):) nhận được một tab (page) hoàn toàn mới, không có cookie, không cache, không lịch sử từ test trước.

    • Ví dụ 2 (Tạo Data): Một fixture create_new_user tạo ra một user với email ngẫu nhiên trong CSDL. Bạn muốn mỗi test đăng ký (test_register_success, test_register_duplicate_email) đều chạy với một user mới hoàn toàn, nên bạn dùng scope="function".


B. scope="class" - Nhóm họp chung bàn

  • Tần suất: Chạy Setup 1 LẦN trước test method đầu tiên trong Class, và Teardown 1 LẦN sau test method cuối cùng trong Class.

  • Analogy (Đời sống): Một bàn làm việc nhóm. Cả nhóm (class) vào phòng họp (setup) lúc 9h sáng. Mọi người (test methods) dùng chung cái bàn đó để làm việc. 5h chiều họp xong, cả nhóm dọn dẹp bàn và rời đi (teardown).

  • Khi nào dùng trong Auto Test:

    • Khi bạn có một nhóm test (trong 1 class) đều yêu cầu chung một bước setup tốn kém và các test đó không phá hỏng trạng thái của nhau.

    • Ví dụ 1 (Login 1 lần): Bạn có class TestProfileSettings: chứa 10 test nhỏ (test_update_avatar, test_update_bio, test_change_address...). Tất cả 10 test này đều yêu cầu user phải đăng nhập.

      • Dùng scope="function": Sẽ phải Login 10 lần và Logout 10 lần ➝ Rất chậm!

      • Dùng scope="class": Tạo 1 fixture logged_in_page_class_scope (scope="class"). Nó sẽ Login 1 LẦN, sau đó cả 10 test case cùng chạy trên page đã đăng nhập đó. Cuối cùng, nó Logout 1 LẦN. ➝ Nhanh hơn rất nhiều.

      • Lưu ý: Để dùng, bạn phải đánh dấu class với @pytest.mark.usefixtures("<tên_fixture>"). Các method trong class sẽ không nhận fixture làm tham số trực tiếp, mà sẽ dùng page (hoặc self.page) đã được fixture đó "biến đổi"


C. scope="module" - Cục phát Wi-Fi của file

  • Tần suất: Chạy 1 lần cho toàn bộ file .py.

  • Analogy (Đời sống): Kết nối Wi-Fi cho một tầng lầu. Khi người đầu tiên của tầng lầu (module) đến, bộ phát Wi-Fi (fixture) được bật lên (setup). Mọi người trong tầng (all functions/classes in the file) dùng chung Wi-Fi đó. Khi người cuối cùng rời đi, bộ phát được tắt (teardown).

  • Khi nào dùng trong Auto Test:

    • Khi bạn cần một tài nguyên cho toàn bộ file test, nhưng không muốn ảnh hưởng đến các file test khác.

    • Ví dụ thực chiến: File test cần truy vấn Database 20 lần để verify data. Bạn tạo Fixture mở Connection DB (scope="module"). Nó kết nối 1 lần lúc bắt đầu chạy file và đóng connection khi toàn bộ file kết thúc.

 

D. scope="session" - Móng nhà của Framework

  • Tần suất: Chạy Setup 1 LẦN DUY NHẤT khi bắt đầu toàn bộ phiên chạy pytest, và Teardown 1 LẦN DUY NHẤT khi tất cả test trong mọi file đã chạy xong.

  • Analogy (Đời sống): Xây dựng tòa nhà. Bạn xây tòa nhà (fixture) 1 LẦN (setup). Sau đó, hàng ngàn người (test cases) ra vào, sử dụng tòa nhà đó trong nhiều năm. Hàng chục năm sau, khi tòa nhà không còn được sử dụng nữa, nó mới bị phá dỡ (teardown).

  • Khi nào dùng trong Auto Test:

    • Cho những tài nguyên siêu tốn kém để tạo ra, và thường là chỉ đọc (read-only), hoặc được dùng chung bởi tất cả các test.

    • Ví dụ : Đọc file config.json (chứa URL môi trường Staging/Live, account test). 2. Khởi động tiến trình (process) Chromium vật lý. Bật trình duyệt tốn vài giây, nên ta chỉ bật 1 lần (scope session), sau đó các test case (scope function) chỉ việc mượn nó để tạo tab (context).


      📄 test_scope_demo.py

    • import pytest
      from playwright.sync_api import sync_playwright
      
      # ==================================================
      # 🔹 FIXTURE 1: scope="function"
      #   → Mỗi test case chạy 1 lần (mặc định)
      # ==================================================
      @pytest.fixture(scope="function")
      def function_fixture():
          print("\n[SETUP] function_fixture")
          yield "function_scope"
          print("[TEARDOWN] function_fixture")
      
      # ==================================================
      # 🔹 FIXTURE 2: scope="class"
      #   → Chạy 1 lần cho mỗi class test
      # ==================================================
      @pytest.fixture(scope="class")
      def class_fixture():
          print("\n[SETUP] class_fixture")
          yield "class_scope"
          print("[TEARDOWN] class_fixture")
      
      # ==================================================
      # 🔹 FIXTURE 3: scope="module"
      #   → Chạy 1 lần cho mỗi file (module)
      # ==================================================
      @pytest.fixture(scope="module")
      def module_fixture():
          print("\n[SETUP] module_fixture")
          with sync_playwright() as p:
              browser = p.chromium.launch(headless=False)
              context = browser.new_context()
              yield context
              print("[TEARDOWN] module_fixture")
              context.close()
              browser.close()
      
      # ==================================================
      # 🔹 FIXTURE 4: scope="session"
      #   → Chạy 1 lần duy nhất cho toàn bộ session pytest
      # ==================================================
      @pytest.fixture(scope="session")
      def session_fixture():
          print("\n[SETUP] session_fixture")
          yield "session_scope"
          print("[TEARDOWN] session_fixture")
      
      # ==================================================
      # 🔹 Test case sử dụng các fixture khác nhau
      # ==================================================
      
      def test_case_one(module_fixture, function_fixture, session_fixture):
          print("\n--- Test Case ONE ---")
          page = module_fixture.new_page()
          page.goto("https://example.com")
          print("Page title:", page.title())
          page.close()
      
      def test_case_two(module_fixture, function_fixture):
          print("\n--- Test Case TWO ---")
          page = module_fixture.new_page()
          page.goto("https://playwright.dev")
          print("Page title:", page.title())
          page.close()
      
      # ==================================================
      # 🔹 Test class để minh họa scope="class"
      # ==================================================
      @pytest.mark.usefixtures("class_fixture", "module_fixture")
      class TestExample:
          def test_inside_class_1(self, function_fixture):
              print("\n>>> test_inside_class_1 running")
      
          def test_inside_class_2(self, function_fixture):
              print("\n>>> test_inside_class_2 running")
      

       

Bảng tổng kết nhanh:

Tiêu chí so sánh scope="function" scope="class" scope="module" scope="session"
Tần suất chạy Setup Trước mỗi hàm test Trước mỗi class test Trước mỗi file .py 1 lần cho cả đợt test
Mức độ an toàn dữ liệu Tối đa (Cô lập hoàn toàn) Trung bình Thấp Rất thấp (Dễ bị ảnh hưởng lẫn nhau)
Tốc độ thực thi Chậm hơn Nhanh Rất nhanh Tối ưu tốc độ cao nhất
Ví dụ thực tế Mở một tab ẩn danh mới tinh để test tính năng Đăng nhập một lần để chạy toàn bộ test trong class Mở kết nối đến Database Khởi động trình duyệt Chromium/Đọc file config


3. Tổ chức chuyên nghiệp với
 conftest.py

Khi dự án automation test phát triển lên hàng trăm file, việc khai báo đi khai báo lại các fixture ở từng file sẽ vi phạm nguyên tắc DRY. Pytest cung cấp một file đặc biệt tên là conftest.py.

Khi bạn đặt các hàm fixture vào trong file conftest.py, Pytest sẽ tự động quét, nhận diện và cho phép tất cả các file test trong cùng thư mục (hoặc thư mục con) gọi trực tiếp các fixture này bằng tên tham số mà không cần sử dụng lệnh import.

📁 Cấu trúc thư mục:

project/
│
├── conftest.py              # Fixtures dùng chung cho toàn bộ project
│
├── pages/
│   ├── base_page.py
│   └── login_page.py
│
└── tests/
    ├── conftest.py          # Fixtures dành riêng cho các test trong thư mục tests
    └── test_login_pom.py


📄 conftest.py

# conftest.py
import pytest
from playwright.sync_api import sync_playwright

@pytest.fixture(scope="session")
def browser_instance():
    p = sync_playwright().start()
    browser = p.chromium.launch(headless=False)
    yield browser
    browser.close()
    p.stop()

@pytest.fixture(scope="function")
def page(browser_instance):
    context = browser_instance.new_context()
    page = context.new_page()
    yield page
    page.close()
    context.close()

 

📄 test_login_pom.py

from playwright.sync_api import expect


def test_login(page):
    page.goto("https://example.com/login")

    username_input = page.locator("#username")
    password_input = page.locator("#password")
    submit_button = page.locator("button[type='submit']")
    heading = page.locator("h1")

    username_input.fill("admin")
    password_input.fill("123456")
    submit_button.click()

    expect(heading).to_have_text("Dashboard")

 

✅ Chạy tất cả test:

pytest -v

💡 pytest sẽ tự động load fixture trong conftest.pykhông cần import thủ công.

4. Điểm mạnh "vô đối" và Mức độ sử dụng trong Automation

Trong thực tế xây dựng Framework, 100% các dự án dùng Pytest đều phải dùng Fixture. Nó là nền móng vì mang lại 3 sức mạnh cốt lõi:

  • Tự động dọn dẹp (Safety First): Không sợ quên đóng trình duyệt (gây tốn RAM) hay quên xóa data rác trong Database. Dù Test Case có bị lỗi (Exception) ngay giữa chừng, đoạn code bên dưới yield (Teardown) vẫn chắc chắn được gọi.

  • Chia sẻ không cần Import (Dependency Injection): Đặt Fixture vào file conftest.py, toàn bộ hàng trăm file test của bạn có thể gọi tên Fixture đó ra dùng ngay lập tức mà không cần 1 dòng import nào.

  • Module hóa cao độ (Tái sử dụng): Một Fixture có thể gọi một Fixture khác. Bạn có thể xây dựng các block logic nhỏ rồi ghép chúng lại với nhau rất mượt mà.

Teacher

Teacher

Hà Lan

QA Automation

With over 5 years of experience in web, API, and mobile test automation, built strong expertise in designing and maintaining automation frameworks across various domains and international projects. Committed to mentoring and knowledge sharing, I provide practical guidance and proven techniques to help aspiring testers develop their skills and succeed in the automation field.

Cộng đồng Automation Testing Việt Nam:

🌱 Telegram Automation Testing:   Cộng đồng Automation Testing
🌱 
Facebook Group Automation: Cộng đồng Automation Testing Việt Nam
🌱 
Facebook Fanpage: Cộng đồng Automation Testing Việt Nam - Selenium
🌱 Telegram
Manual Testing:   Cộng đồng Manual Testing
🌱 
Facebook Group Manual: Cộng đồng Manual Testing Việt Nam

Chia sẻ khóa học lên trang

Bạn có thể đăng khóa học của chính bạn lên trang Anh Tester để kiếm tiền

Danh sách bài học