0
0
Lập trình
Thaycacac
Thaycacac thaycacac

Tại sao kiểm thử E2E và tích hợp ổn định là cần thiết?

Đăng vào 1 tháng trước

• 9 phút đọc

Tại sao kiểm thử E2E và tích hợp ổn định là cần thiết?

Giới thiệu

Kiểm thử end-to-end (E2E) và tích hợp là những thành phần thiết yếu giúp duy trì sự tự tin trong sản phẩm của bạn. Chúng xác thực các luồng người dùng và tương tác của hệ thống. Tuy nhiên, khi các bài kiểm thử không ổn định—có thể thất bại theo cách không liên quan đến mã—chúng cản trở việc phát hành, gây lãng phí thời gian gỡ lỗi và làm giảm lòng tin.

Bài viết này sẽ trình bày một cách tiếp cận thực tiễn để ổn định các bài kiểm thử bằng cách chạy chúng trong Docker với một hình ảnh xem trước của ứng dụng, kết nối với một bản sao cơ sở dữ liệu nhỏ gọn. Kết quả là các bài kiểm thử sẽ chạy nhanh, ổn định và có thể lặp lại.


Vấn đề với các bài kiểm thử không ổn định

Các nguồn gây ra sự không ổn định:

  • Môi trường chia sẻ: Các bài kiểm thử truy cập vào một cơ sở dữ liệu chính/chia sẻ lớn và liên tục thay đổi.
  • Phụ thuộc dữ liệu không kiểm soát: Các thực thể cần thiết có thể bị thiếu hoặc thay đổi theo thời gian.
  • Cơ sở dữ liệu nặng nề: Các bản sao đầy đủ mất nhiều thời gian để chụp nhanh, khôi phục và di chuyển, đặc biệt trong CI.
  • Sự thay đổi môi trường: Các phiên bản thư viện, dịch vụ hoặc cờ tính năng khác nhau giữa dev/CI/prod.

Giải pháp: Môi trường Docker tách biệt + Bản sao cơ sở dữ liệu nhỏ gọn

Chúng tôi cải thiện độ ổn định bằng cách tách biệt mọi thứ cho mỗi lần chạy kiểm thử:

  1. Hình ảnh Docker xem trước của ứng dụng (xây dựng từ nhánh hiện tại/PR).
  2. Container cơ sở dữ liệu riêng biệt (mới cho mỗi lần chạy) với một tập dữ liệu nhỏ, được biết đến là tốt.
  3. Gieo hạt có thể lặp lại: Một hạt gieo có thể lặp lại chỉ chứa các thực thể và mối quan hệ tối thiểu cần thiết.

Lợi ích:

  • Dữ liệu ổn định, hành vi dự đoán được, thiết lập nhanh hơn và ít trường hợp sai sót hơn.

Cài đặt tham khảo (Docker Compose)

Dưới đây là một tệp Compose tối thiểu cho một ứng dụng web và một cơ sở dữ liệu Postgres. Hãy điều chỉnh tên dịch vụ, cổng và biến môi trường cho ngăn xếp của bạn.

yaml Copy
version: "3.9"
services:
  app:
    image: ghcr.io/your-org/your-app:${GIT_SHA:-preview}
    depends_on:
      - db
    environment:
      NODE_ENV: test
      DATABASE_URL: postgres://testuser:testpass@db:5432/testdb
    ports:
      - "8080:8080"
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
      interval: 5s
      timeout: 2s
      retries: 20

  db:
    image: postgres:16
    environment:
      POSTGRES_USER: testuser
      POSTGRES_PASSWORD: testpass
      POSTGRES_DB: testdb
    ports:
      - "5433:5432"
    volumes:
      - db_data_test:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U testuser -d testdb"]
      interval: 5s
      timeout: 2s
      retries: 20

volumes:
  db_data_test:

Xây dựng một hình ảnh xem trước

Xây dựng hình ảnh xem trước trong CI từ nhánh hiện tại và gán nó với SHA cam kết:

dockerfile Copy
# Ví dụ ứng dụng Node
FROM node:20-alpine AS deps
WORKDIR /app
COPY package*.json ./
RUN npm ci

FROM node:20-alpine AS build
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN npm run build

FROM node:20-alpine
WORKDIR /app
ENV NODE_ENV=production
COPY --from=build /app .
EXPOSE 8080
CMD ["node", "dist/server.js"]

Gieo hạt có thể lặp lại: Bản sao cơ sở dữ liệu nhỏ gọn

Một bản sao nhỏ gọn không phải là một bản sao đầy đủ của master/prod. Nó là một tập dữ liệu được tuyển chọn chỉ chứa các bảng/hàng cần thiết cho kịch bản kiểm thử của bạn—người dùng, vai trò, cờ tính năng, dữ liệu danh mục tối thiểu, v.v.

Ví dụ (Postgres) gieo hạt

sql Copy
-- Vai trò tối thiểu
INSERT INTO roles (id, name) VALUES 
  ('00000000-0000-0000-0000-000000000001', 'admin'),
  ('00000000-0000-0000-0000-000000000002', 'user')
ON CONFLICT (id) DO NOTHING;

-- Người dùng kiểm thử
INSERT INTO users (id, email, role_id, created_at)
VALUES ('10000000-0000-0000-0000-000000000001', 'testuser@example.com',
        '00000000-0000-0000-0000-000000000002', NOW())
ON CONFLICT (id) DO NOTHING;

-- Cờ tính năng (chỉ những cờ cần thiết cho các luồng đang kiểm thử)
INSERT INTO feature_flags (key, enabled)
VALUES ('checkout_v2', true)
ON CONFLICT (key) DO UPDATE SET enabled = EXCLUDED.enabled;

-- Thực thể miền thiết yếu cho các luồng E2E
INSERT INTO products (id, name, price_cents)
VALUES (1, 'Sample Product', 1999)
ON CONFLICT (id) DO NOTHING;

Áp dụng các migration và gieo hạt trong CI

Sử dụng một Makefile đơn giản để tổ chức các bước vòng đời:

makefile Copy
.PHONY: up migrate seed test down

up:
    docker compose -f docker-compose.test.yml up -d --wait

migrate:
    # Thay thế bằng công cụ migration của bạn, ví dụ: Prisma, Knex, Flyway, Liquibase
    docker compose -f docker-compose.test.yml exec -T app npm run migrate:up

seed:
    docker compose -f docker-compose.test.yml exec -T db \
      psql -U testuser -d testdb < seed/slim-seed.sql

# Ví dụ: Playwright/Cypress/WDIO/v.v.
test:
    docker compose -f docker-compose.test.yml exec -T app npm run test:e2e

down:
    docker compose -f docker-compose.test.yml down -v

Sau đó trong CI (ví dụ GitHub Actions):

yaml Copy
name: e2e
on: [pull_request]

jobs:
  e2e:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Xây dựng hình ảnh xem trước
        run: |
          docker build -t ghcr.io/your-org/your-app:${{ github.sha }} .
      - name: Bắt đầu ngăn xếp
        run: |
          GIT_SHA=${{ github.sha }} make up
      - name: Di chuyển & gieo hạt
        run: |
          make migrate
          make seed
      - name: Chạy kiểm thử
        run: |
          make test
      - name: Dọn dẹp
        if: always()
        run: |
          make down

Tạo một bản sao nhỏ từ một cơ sở dữ liệu lớn (Tùy chọn)

Nếu bạn phải lấy tập dữ liệu nhỏ từ nguồn lớn, đừng sao chép tất cả. Chỉ lấy những hàng tối thiểu và duy trì tính toàn vẹn tham chiếu.

Chiến lược A: Gieo hạt thủ công (được khuyến nghị)

  • Viết các câu lệnh INSERT phi idempotent cho một số thực thể cần thiết.
  • Giữ nó trong kiểm soát phiên bản và xem xét các thay đổi cùng với các bài kiểm thử.

Chiến lược B: Xuất chọn lọc (ví dụ Postgres)

Sử dụng pg_dump với danh sách bao gồm và bộ lọc:

bash Copy
# Xuất chỉ schema (migrations nhanh)
pg_dump \
  --schema-only \
  --no-owner --no-privileges \
  "$DATABASE_URL" > schema.sql

# Xuất dữ liệu tối thiểu cho các bảng cụ thể
pg_dump \
  --data-only \
  --table=roles \
  --table=feature_flags \
  --table=products \
  --column-inserts \
  "$DATABASE_URL" > minimal-data.sql

cat schema.sql minimal-data.sql > seed/slim-seed.sql

Chiến lược C: Xuất theo chương trình

  • Viết một script nhỏ (Node/TS) chỉ truy vấn các hàng/cột cần thiết và phát hành các dữ liệu SQL/JSON.
  • Bắt buộc ID ổn định (UUID) cho các tham chiếu có thể xác định trong các lần chạy.

Mẹo: Giữ cho các hạt nhỏ (dưới vài trăm hàng). Nếu một bài kiểm thử cần dữ liệu mới, mở rộng dần hạt gieo và thêm một xác nhận để chứng minh điều đó.


Chạy kiểm thử trong ngăn xếp

Trình chạy kiểm thử của bạn (ví dụ: Playwright, Cypress, WebdriverIO, Jest-integration) nên:

  • Chờ kiểm tra sức khỏe của ứng dụng và cơ sở dữ liệu.
  • Sử dụng một BASE_URL duy nhất đã biết (ví dụ: http://app:8080).
  • Tạo dữ liệu kiểm thử tạm thời trên cơ sở khi cần thiết, hoặc tái sử dụng các hạt gieo.

Ví dụ đoạn cấu hình Playwright:

javascript Copy
import { defineConfig } from '@playwright/test';

export default defineConfig({
  use: {
    baseURL: process.env.BASE_URL || 'http://localhost:8080',
    trace: 'retain-on-failure',
    video: 'retain-on-failure',
  },
});

Chạy lệnh:

bash Copy
BASE_URL=http://localhost:8080 npx playwright test

Khắc phục sự không ổn định: Danh sách kiểm tra ngắn

  • Thời gian: Thay thế việc sử dụng wait tùy ý bằng các wait rõ ràng cho trạng thái mạng/DOM.
  • Dữ liệu: Đảm bảo các hạt được áp dụng và idempotent; đặt lại DB giữa các kịch bản nếu cần.
  • Tách biệt: Không bài kiểm thử nào nên phụ thuộc vào trạng thái từ các bài kiểm thử trước đó.
  • Đồng hồ: Giả mạo thời gian nếu các luồng phụ thuộc vào thời gian (mã thông báo, hết hạn, cron).
  • API bên ngoài: Stub/mirror các cuộc gọi của bên thứ ba; không để chúng làm hỏng CI của bạn.
  • Retry: Sử dụng retry cấp độ kiểm thử một cách tiết kiệm và chỉ cho các điểm đã biết không ổn định.

Kết luận

Các bài kiểm thử ổn định là nền tảng của việc phát hành đáng tin cậy. Bằng cách chạy các bài kiểm thử E2E/tích hợp trong Docker với một hình ảnh ứng dụng xem trước và một bản sao cơ sở dữ liệu nhỏ gọn, bạn loại bỏ sự trôi dạt dữ liệu và kết nối môi trường. Các bộ kiểm thử của bạn trở nên nhanh hơn, có thể xác định và dễ tin tưởng hơn.

Trong thực tiễn, các đội ngũ áp dụng cách tiếp cận này đã thấy giảm mạnh tỉ lệ thất bại trong kiểm thử do các vấn đề về dữ liệu hoặc môi trường, đồng thời đạt được tăng tốc độ thực thi kiểm thử đáng kể. Các chu kỳ phản hồi nhanh hơn và ít báo động sai hơn trực tiếp dẫn đến năng suất của lập trình viên và tự tin hơn trong các bản phát hành.

Nếu đội ngũ của bạn đang phải đấu tranh với các bài kiểm thử không ổn định, hãy thử cách tiếp cận này. Bắt đầu với một hạt gieo nhỏ, thủ công; tự động hóa các migration; và giữ mọi thứ trong Compose. Nỗ lực ban đầu sẽ nhanh chóng mang lại lợi ích trong việc tiết kiệm thời gian kỹ thuật và bản phát hành an toàn hơn.


Tác động thực tế (Ví dụ số liệu)

Dưới đây là một cái nhìn trước/sau đơn giản mà nhiều đội ngũ quan sát khi chuyển sang Docker + bản sao cơ sở dữ liệu nhỏ:

Chỉ số Trước (DB chia sẻ) Sau (DB nhỏ trong Docker)
Thời gian chạy E2E trung bình ~45 phút ~18 phút
Tỉ lệ thất bại bài kiểm thử không ổn định 20–30% các lần chạy <5% các lần chạy
Sự tự tin của lập trình viên Thấp Cao

Ngay cả những số liệu gần đúng như thế này cũng tạo ra một lập luận mạnh mẽ trong các bài viết và bài thuyết trình nội bộ.


Phụ lục: Biến thể MongoDB (Bonus)

Đối với MongoDB, thay thế các dịch vụ/lệnh Postgres bằng các hình ảnh Mongo và mongorestore:

yaml Copy
version: "3.9"
services:
  app:
    image: ghcr.io/your-org/your-app:${GIT_SHA:-preview}
    environment:
      MONGODB_URI: mongodb://testuser:testpass@mongo:27017/testdb?authSource=admin
    depends_on:
      - mongo

  mongo:
    image: mongo:7
    ports:
      - "27018:27017"
    environment:
      MONGO_INITDB_ROOT_USERNAME: testuser
      MONGO_INITDB_ROOT_PASSWORD: testpass

Ví dụ gieo hạt:

bash Copy
# Dữ liệu JSON hoặc bsondump giữ nhỏ và được kiểm soát phiên bản
mongorestore --uri "mongodb://testuser:testpass@localhost:27018/testdb?authSource=admin" \
  --drop ./seed/mongo/slim
Gợi ý câu hỏi phỏng vấn
Không có dữ liệu

Không có dữ liệu

Bài viết được đề xuất
Bài viết cùng tác giả

Bình luận

Chưa có bình luận nào

Chưa có bình luận nào