Product Management
Đăng nhập
ESC

Nhập từ khóa để tìm kiếm

↑↓ Di chuyển
Enter Mở
ESC Đóng

GitHub Actions — modern CI for QA

Mở đầu — vì sao bài này quan trọng

Hãy tưởng tượng bạn vừa viết xong một bộ test tự động rất ngon: Selenium chạy mượt, API test xanh lè, unit test đầy đủ. Nhưng chúng chỉ chạy trên máy bạn. Mỗi lần ai đó trong team push code, chẳng ai chạy lại test cả — cho tới khi khách hàng phát hiện bug trên production. Đây là câu chuyện có thật ở rất nhiều startup Việt Nam giai đoạn đầu: có test, nhưng không có "người gác cổng" tự động.

GitHub Actions chính là người gác cổng đó. Nó là hệ thống CI/CD (Continuous Integration / Continuous Delivery) tích hợp sẵn ngay trong repo GitHub — không cần cài server riêng, không cần một anh DevOps ngồi dựng Jenkins (bài 22 chúng ta đã học Jenkins rồi). Chỉ cần thêm một file YAML vào repo, mỗi lần có commit hoặc pull request, GitHub sẽ tự khởi động một máy ảo, cài dependencies, chạy toàn bộ test của bạn, rồi báo pass/fail ngay trên giao diện pull request.

Với người làm QA/SDET, GitHub Actions đặc biệt quan trọng vì hai lý do. Thứ nhất, nó biến test tự động của bạn từ "thứ chạy khi ai đó nhớ ra" thành "thứ luôn chạy, không thể bỏ qua". Thứ hai, nó gần như miễn phí để bắt đầu: repo public chạy không giới hạn phút, repo private được 2000 phút/tháng ở gói free — quá đủ cho một team nhỏ hoặc cho bạn học tập, làm portfolio.

Bài này không dạy lại Jenkins hay GitHub Actions dưới góc độ deploy; chúng ta tập trung hoàn toàn vào việc dùng GitHub Actions như một CI hiện đại để chạy test QA.

Khái niệm cốt lõi

Workflow, Job, Step, Action

GitHub Actions có bốn khái niệm bạn phải nắm vững, xếp từ lớn đến nhỏ:

  • Workflow: một quy trình tự động, được định nghĩa bằng một file YAML đặt trong thư mục .github/workflows/. Một repo có thể có nhiều workflow (ví dụ một cái chạy test, một cái deploy).
  • Job: một workflow gồm nhiều job. Mỗi job chạy trên một máy ảo (runner) riêng, mặc định các job chạy song song.
  • Step: mỗi job gồm nhiều step chạy tuần tự. Một step có thể là một lệnh shell (run) hoặc gọi một action có sẵn (uses).
  • Action: đơn vị tái sử dụng do cộng đồng hoặc GitHub cung cấp, ví dụ actions/checkout để lấy code về, actions/setup-node để cài Node.js.

Trigger — khi nào workflow chạy

Phần on: quyết định workflow được kích hoạt lúc nào. Với QA, ba trigger quan trọng nhất là:

  • push: khi có code đẩy lên (thường giới hạn ở branch main hoặc develop).
  • pull_request: khi mở hoặc cập nhật PR — đây là lúc chạy test quan trọng nhất, để chặn code lỗi trước khi merge.
  • schedule: chạy theo lịch cron, ví dụ mỗi đêm chạy full regression suite (nightly build).
  • workflow_dispatch: cho phép bấm nút chạy thủ công trên giao diện GitHub.

Runner

Runner là máy ảo thực thi job. GitHub cung cấp sẵn runner với ubuntu-latest, windows-latest, macos-latest. Đa số test QA chạy trên ubuntu-latest vì nhanh và rẻ nhất (Windows tốn gấp 2 lần phút, macOS gấp 10 lần — con số này rất quan trọng khi tính chi phí).

File workflow cơ bản

Đây là một workflow tối thiểu chạy test cho dự án Node.js:

.github/workflows/test.yml

name: QA Test Suite

on: push: branches: [main, develop] pull_request: branches: [main]

jobs: test: runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v4

- name: Setup Node.js uses: actions/setup-node@v4 with: node-version: '20' cache: 'npm'

- name: Install dependencies run: npm ci

- name: Run tests run: npm test

Chỉ với chừng đó dòng, mỗi PR vào main sẽ tự động chạy test và hiển thị dấu tick xanh hoặc dấu X đỏ ngay trong PR.

Matrix — chạy nhiều phiên bản cùng lúc

Một tính năng cực mạnh cho QA là matrix strategy: chạy cùng một job trên nhiều cấu hình song song. Ví dụ test trên nhiều phiên bản Node cùng lúc:

jobs:
  test:
    runs-on: ubuntu-latest
    strategy:
      fail-fast: false
      matrix:
        node-version: [18, 20, 22]
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node-version }}
      - run: npm ci
      - run: npm test

fail-fast: false rất quan trọng: mặc định nếu một cấu hình fail, GitHub sẽ hủy các cấu hình còn lại. Với QA, bạn thường muốn thấy TẤT CẢ đều fail ở đâu, nên hãy tắt fail-fast đi.

Secrets — đừng bao giờ hardcode

Test QA thường cần token, mật khẩu database staging, API key sandbox. Tuyệt đối không viết thẳng vào file YAML. GitHub cung cấp Secrets (Settings → Secrets and variables → Actions). Truy cập bằng ${{ secrets.TEN_SECRET }}:

      - name: Run API tests
        run: npm run test:api
        env:
          API_TOKEN: ${{ secrets.STAGING_API_TOKEN }}
          DB_PASSWORD: ${{ secrets.STAGING_DB_PASSWORD }}

Artifacts — giữ lại bằng chứng

Khi test fail, bạn cần bằng chứng: screenshot, log, HTML report. Dùng actions/upload-artifact để lưu lại và tải về sau:

      - name: Upload test report
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: test-report
          path: reports/
          retention-days: 7

Chú ý if: always() — nếu không có dòng này, khi test fail step upload sẽ bị bỏ qua và bạn mất luôn bằng chứng.

Tình huống thực tế

Tình huống 1: Startup fintech ở TP.HCM chặn bug trước khi merge

Một startup ví điện tử ở Quận 1 (gọi là PayViet) có team 6 dev, 2 QA. Trước đây quy trình là: dev push code lên, QA test tay trên staging. Trung bình mỗi tuần lọt 2–3 bug hồi quy (regression) ra staging vì không ai chạy lại bộ test cũ.

Bạn QA lead quyết định đưa bộ API test (viết bằng Python + pytest, bài 16 của khóa này) vào GitHub Actions, trigger theo pull_request. Cấu hình thêm branch protection rule: PR không thể merge nếu workflow test fail. Kết quả sau 2 tháng: số bug hồi quy lọt ra staging giảm từ ~2,5/tuần xuống gần 0. Quan trọng hơn, dev tự sửa bug ngay khi mở PR vì họ thấy X đỏ, không đợi QA nhắc.

Bài học: sức mạnh thật của GitHub Actions không nằm ở việc "chạy test" mà ở việc kết hợp với branch protection để BIẾN test thành điều kiện bắt buộc để merge. Test chạy mà không chặn được merge thì chỉ là trang trí.

Tình huống 2: Công ty gia công phần mềm ở Đà Nẵng và bài toán chi phí phút

Một công ty outsource ở Đà Nẵng có repo private, chạy bộ Selenium E2E khá nặng (khoảng 12 phút mỗi lần). Ban đầu họ cấu hình chạy trên MỌI push của MỌI branch. Cuối tháng họ giật mình: đã tiêu hết 2000 phút free và bị tính thêm phí. Đào sâu ra thì mỗi dev push chục lần một ngày, mỗi lần lại kích hoạt 12 phút.

Họ tối ưu ba việc: (1) chỉ chạy E2E full khi có pull_request vào main, còn push branch thường chỉ chạy unit test nhanh; (2) dùng paths filter để bỏ qua khi chỉ sửa file README hay tài liệu; (3) dùng concurrency để hủy các lần chạy cũ khi có push mới trên cùng PR. Sau tối ưu, số phút tiêu thụ giảm khoảng 60%, đủ nằm trong hạn mức free.

on:
  pull_request:
    branches: [main]
    paths-ignore:
      - '**.md'
      - 'docs/**'

concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true

Bài học: phút CI là tài nguyên có giá. Đừng chạy mọi thứ ở mọi nơi. Phân tầng test — nhanh chạy thường xuyên, nặng chạy chọn lọc — vừa tiết kiệm tiền vừa tiết kiệm thời gian chờ.

Tình huống 3: Team e-commerce chạy nightly regression bằng schedule

Một sàn thương mại điện tử ở Hà Nội có bộ regression khổng lồ (~45 phút). Chạy bộ này ở mỗi PR sẽ làm dev chờ mòn mỏi. Giải pháp: bộ smoke test nhanh (3 phút) chạy ở mỗi PR, còn full regression chạy tự động lúc 2 giờ sáng mỗi ngày bằng schedule, kết quả gửi vào kênh Slack của team.

on:
  schedule:
    - cron: '0 19   *'  # 2h sáng giờ VN (UTC+7 => 19:00 UTC)

Lưu ý cực kỳ hay bị sai: cron trong GitHub Actions dùng giờ UTC, không phải giờ Việt Nam. Muốn chạy 2h sáng VN thì phải trừ đi 7 tiếng, tức 19:00 UTC hôm trước. Team này lúc đầu để 0 2 * và cứ thắc mắc sao test chạy vào 9h sáng.

Bài học: tách smoke test (chạy PR) và full regression (chạy nightly) là chiến lược kinh điển giúp cân bằng giữa tốc độ phản hồi và độ phủ. Và luôn nhớ: cron là giờ UTC.

Hướng dẫn từng bước

Giả sử bạn có một dự án với bộ test sẵn. Đây là các bước để đưa nó lên GitHub Actions:

  • Đảm bảo test chạy được bằng một lệnh duy nhất. Ví dụ npm test, pytest, hoặc mvn test. Nếu trên máy bạn phải làm 5 thao tác thủ công mới chạy được test, hãy gói chúng thành một script trước. CI ghét những thứ chạy được "chỉ trên máy tôi".
  • Tạo thư mục và file workflow: tạo .github/workflows/test.yml ngay trong repo. GitHub tự nhận diện mọi file .yml trong thư mục này.
  • Khai báo trigger: quyết định workflow chạy khi nào. Khởi đầu an toàn là pull_request vào main cộng thêm workflow_dispatch để bấm chạy tay khi test thử.
  • Viết job: khai báo runs-on: ubuntu-latest, rồi lần lượt các step — checkout code, cài môi trường (Node/Python/Java), cài dependencies, chạy test.
  • Thêm secrets nếu cần: vào Settings → Secrets and variables → Actions → New repository secret. Đặt token staging, mật khẩu DB vào đây, rồi tham chiếu qua ${{ secrets.TEN }}.
  • Commit và push. Ngay khi push, vào tab Actions trên GitHub, bạn sẽ thấy workflow bắt đầu chạy. Bấm vào để xem log realtime từng step.
  • Bật branch protection: Settings → Branches → Add rule cho main → tick "Require status checks to pass before merging" và chọn job test của bạn. Từ giờ, không PR nào lọt qua nếu test đỏ.
  • Thêm artifact và badge: cấu hình upload report khi fail, và thêm badge trạng thái vào README (
    Tests
    Tests
    ) để cả team nhìn thấy sức khỏe của repo.

Lỗi thường gặp & mẹo

  • Quên actions/checkout: nhiều người mới viết workflow mà không có step checkout, rồi ngạc nhiên vì runner báo "không tìm thấy file". Runner khởi động RỖNG, bạn phải chủ động checkout code về.
  • Dùng npm install thay vì npm ci: trong CI hãy dùng npm ci, nó cài chính xác theo package-lock.json, nhanh hơn và tránh trường hợp version lệch làm test pass/fail thất thường.
  • Test cần browser mà chưa cài: chạy Selenium/Playwright trên runner cần browser. Với Playwright dùng npx playwright install --with-deps. Với Selenium headless, runner Ubuntu đã có Chrome sẵn nhưng nhớ chạy chế độ headless (bài 25).
  • Hardcode secret vào YAML: file workflow nằm trong repo, ai đọc được code là đọc được token. Luôn dùng Secrets. Đặc biệt với repo public, lộ key là chuyện nghiêm trọng.
  • Quên if: always() khi upload report: nếu step upload artifact không có dòng này, khi test fail nó sẽ bị skip và bạn mất bằng chứng — đúng lúc cần nhất.
  • Cron nhầm múi giờ: như tình huống 3, cron là UTC. Việt Nam là UTC+7, luôn trừ 7 tiếng.
  • Chạy quá nhiều, đốt phút: dùng paths-ignore, concurrency: cancel-in-progress, và phân tầng test để tiết kiệm.
  • Mẹo cache: actions/setup-nodesetup-python có option cache để lưu lại dependencies giữa các lần chạy, có thể rút ngắn thời gian install từ vài phút xuống vài giây.
  • Mẹo debug: khi workflow fail bí ẩn, thêm một step run: env hoặc bật debug log bằng secret ACTIONS_STEP_DEBUG: true để xem chi tiết. Bạn cũng có thể chạy thử workflow ở local bằng công cụ act trước khi push.

Bài tập thực hành

  • Bài cơ bản: Tạo một repo GitHub (public để chạy free không giới hạn), thêm một bộ test nhỏ bằng ngôn ngữ bạn quen (pytest, Jest, hoặc JUnit). Viết file .github/workflows/test.yml chạy test khi có pull_request. Mở một PR và xác nhận thấy dấu tick xanh.
  • Bài trung cấp: Thêm matrix strategy để chạy test trên 3 phiên bản ngôn ngữ (ví dụ Python 3.10, 3.11, 3.12). Cố tình viết một test fail ở một phiên bản, quan sát xem GitHub báo cáo thế nào. Bật fail-fast: false và so sánh khác biệt.
  • Bài nâng cao: Cấu hình một secret giả (ví dụ TEST_TOKEN), truyền vào test qua biến môi trường. Thêm step upload artifact để lưu report khi test fail (nhớ if: always()). Bật branch protection cho main. Cuối cùng, thêm một workflow schedule chạy lúc 2h sáng giờ Việt Nam — tự tính đúng giờ UTC.

Tóm tắt

GitHub Actions là hệ thống CI/CD tích hợp sẵn trong GitHub, cho phép bạn tự động chạy test QA mỗi khi có commit hay pull request mà không cần dựng server riêng. Bốn khái niệm cốt lõi là Workflow, Job, Step và Action; ba trigger quan trọng nhất cho QA là push, pull_requestschedule. Sức mạnh thật sự đến khi bạn kết hợp workflow với branch protection để biến test thành điều kiện bắt buộc để merge — chặn bug ngay từ cửa PR.

Những kỹ thuật cần nắm: matrix để test đa cấu hình song song, secrets để bảo vệ thông tin nhạy cảm, artifacts để giữ bằng chứng khi test fail, và tối ưu chi phí phút bằng paths-ignore, concurrency cùng chiến lược phân tầng smoke/regression. Nhớ những cạm bẫy kinh điển: quên checkout, hardcode secret, và cron chạy theo giờ UTC. Nắm vững GitHub Actions, bạn đã biến bộ test tự động của mình từ "thứ chạy trên máy tôi" thành người gác cổng chất lượng luôn túc trực cho cả team.

Học xong bài này rồi? Tạo tài khoản miễn phí để lưu lại — lần sau vào là biết ngay đang dở ở đâu, và học hết khóa thì có chứng chỉ. Lưu tiến độ của tôi