+3

Từ Zero đến Principal Frontend Engineer (P7: Chiến lược Testing Frontend - Từ Unit Test đến E2E)

Phần 1: Tại Sao Testing Là "Non-Negotiable"?

1.1 Hậu quả của Poor Testing

  • Case study: Một công ty mất $2.4 triệu do bug checkout không được phát hiện vì thiếu integration test.
  • Số liệu: 40% bug frontend đến từ state management sai (Redux, Context API) – có thể ngăn chặn bằng unit test.

1.2 Tư Duy Testing

  • Testing như documentation: Mỗi test case phải mô tả business requirement.
  • Cost of change: Chi phí sửa bug tăng gấp 10 lần nếu phát hiện muộn (theo IBM Systems Sciences Institute).

Phần 2: Chi Tiết Từng Tầng Testing Pyramid

2.1 Unit Testing: Nền Tảng Của Reliability

Công cụ tối ưu:

  • Jest (cho React/Vue) hoặc Vitest (tốc độ cực nhanh với Vite).
  • React Testing Library (khuyến khích test behavior thay vì implementation).

Ví dụ Phức Tạp: Test Custom Hook với Async Logic

// useAuthHook.js
export const useAuth = () => {
  const [user, setUser] = useState(null);

  const login = async (email, password) => {
    const response = await api.login(email, password);
    setUser(response.data.user);
  };

  return { user, login };
};

// useAuthHook.test.js
test('login updates user state', async () => {
  jest.spyOn(api, 'login').mockResolvedValue({ data: { user: { id: 1, name: 'John' } } });

  let hookResult;
  function TestComponent() {
    hookResult = useAuth();
    return null;
  }
  render(<TestComponent />);

  await act(async () => {
    await hookResult.login('test@example.com', 'password');
  });

  expect(hookResult.user).toEqual({ id: 1, name: 'John' });
});

Best Practices:

  • Mocking chuẩn: Sử dụng jest.mock cho module phức tạp (ví dụ: API calls).
  • Coverage có mục tiêu: 100% cho core business logic, 70% cho UI components.

2.2 Integration Testing: Kịch Bản Thực Tế

Công cụ đề xuất:

  • MSW (Mock Service Worker): Mock API cực mạnh, hỗ trợ GraphQL/REST.
  • Testing Library render với Redux Provider.

Ví dụ: Test Form Submit với API Call và State Update

// LoginForm.test.js
import { setupServer } from 'msw/node';
import { rest } from 'msw';

const server = setupServer(
  rest.post('/api/login', (req, res, ctx) => {
    return res(ctx.json({ token: 'fake-token' }));
  })
);

beforeAll(() => server.listen());
afterAll(() => server.close());

test('submits login form and stores token', async () => {
  const mockStore = configureMockStore();
  const store = mockStore({});

  render(
    <Provider store={store}>
      <LoginForm />
    </Provider>
  );

  fireEvent.change(screen.getByLabelText('Email'), { target: { value: 'test@example.com' } });
  fireEvent.change(screen.getByLabelText('Password'), { target: { value: 'password' } });
  fireEvent.click(screen.getByRole('button', { name: 'Login' }));

  await waitFor(() => {
    const actions = store.getActions();
    expect(actions[0].type).toEqual('auth/loginSuccess');
    expect(actions[0].payload).toEqual('fake-token');
  });
});

Lỗi Thường Gặp:

  • Quá nhiều mock: Dẫn đến test không giống production. Giải pháp: Dùng MSW để mock ở network level.
  • Test quá rộng: Một test case kiểm tra cả UI + API + State. Nên tách thành nhiều test nhỏ.

2.3 E2E Testing: Tập Trung Vào Critical Paths

So Sánh Công Cụ:

Tiêu Chí Cypress Playwright
Tốc độ Chậm hơn, chạy trên browser thật Nhanh, hỗ trợ multi-browser
Hỗ trợ Mobile Giới hạn Mạnh (device emulation)
Parallel execution Cần CI setup phức tạp Hỗ trợ sẵn

Ví dụ Playwright Test cho Checkout Flow:

// checkout.spec.ts
import { test, expect } from '@playwright/test';

test('complete checkout as guest', async ({ page }) => {
  await page.goto('/products/1');
  await page.click('text=Add to Cart');
  await page.click('#cart-icon');
  await page.fill('input[name="email"]', 'test@example.com');
  await page.click('text=Proceed to Checkout');
  await expect(page).toHaveURL('/checkout');
  await page.click('text=Place Order');
  await expect(page.locator('.order-confirmation')).toBeVisible();
});

Chiến Lược E2E Hiệu Quả:

  • Chỉ test happy paths: Login, Checkout, Search.
  • Dùng tagging: @smoke, @regression để phân loại test suite.
  • Kết hợp với monitoring: Sentry để bắt lỗi production chưa được test.

Phần 3: Chiến Lược Duy Trì Test Suite

3.1 Tích Hợp Vào CI/CD

# GitHub Actions example
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: npm install
      - run: npm run test:unit -- --coverage
      - run: npm run test:integration
      - run: npx playwright test
      - uses: actions/upload-artifact@v3
        if: failure()
        with:
          name: playwright-report
          path: playwright-report/

3.2 Đo Lường Hiệu Quả

  • Số liệu cần track:
    • Bug escape rate: Số bug lọt vào production / tổng bug.
    • Test flakiness: Tỉ lệ test fail không ổn định.
  • Tool: Jest Sonar reporter, Cypress Dashboard.

Kết Luận

  • Testing không phải để đạt coverage 100%, mà để giảm rủi ro business.
  • Luôn đặt câu hỏi: "Nếu test này fail, có ảnh hưởng đến user không?".

All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí