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
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 };
};
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
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:
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
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?".