You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

пре 5 дана
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473
  1. <!-- Powered by BMAD-CORE™ -->
  2. # Test Levels Framework
  3. Comprehensive guide for determining appropriate test levels (unit, integration, E2E) for different scenarios.
  4. ## Test Level Decision Matrix
  5. ### Unit Tests
  6. **When to use:**
  7. - Testing pure functions and business logic
  8. - Algorithm correctness
  9. - Input validation and data transformation
  10. - Error handling in isolated components
  11. - Complex calculations or state machines
  12. **Characteristics:**
  13. - Fast execution (immediate feedback)
  14. - No external dependencies (DB, API, file system)
  15. - Highly maintainable and stable
  16. - Easy to debug failures
  17. **Example scenarios:**
  18. ```yaml
  19. unit_test:
  20. component: 'PriceCalculator'
  21. scenario: 'Calculate discount with multiple rules'
  22. justification: 'Complex business logic with multiple branches'
  23. mock_requirements: 'None - pure function'
  24. ```
  25. ### Integration Tests
  26. **When to use:**
  27. - Component interaction verification
  28. - Database operations and transactions
  29. - API endpoint contracts
  30. - Service-to-service communication
  31. - Middleware and interceptor behavior
  32. **Characteristics:**
  33. - Moderate execution time
  34. - Tests component boundaries
  35. - May use test databases or containers
  36. - Validates system integration points
  37. **Example scenarios:**
  38. ```yaml
  39. integration_test:
  40. components: ['UserService', 'AuthRepository']
  41. scenario: 'Create user with role assignment'
  42. justification: 'Critical data flow between service and persistence'
  43. test_environment: 'In-memory database'
  44. ```
  45. ### End-to-End Tests
  46. **When to use:**
  47. - Critical user journeys
  48. - Cross-system workflows
  49. - Visual regression testing
  50. - Compliance and regulatory requirements
  51. - Final validation before release
  52. **Characteristics:**
  53. - Slower execution
  54. - Tests complete workflows
  55. - Requires full environment setup
  56. - Most realistic but most brittle
  57. **Example scenarios:**
  58. ```yaml
  59. e2e_test:
  60. journey: 'Complete checkout process'
  61. scenario: 'User purchases with saved payment method'
  62. justification: 'Revenue-critical path requiring full validation'
  63. environment: 'Staging with test payment gateway'
  64. ```
  65. ## Test Level Selection Rules
  66. ### Favor Unit Tests When:
  67. - Logic can be isolated
  68. - No side effects involved
  69. - Fast feedback needed
  70. - High cyclomatic complexity
  71. ### Favor Integration Tests When:
  72. - Testing persistence layer
  73. - Validating service contracts
  74. - Testing middleware/interceptors
  75. - Component boundaries critical
  76. ### Favor E2E Tests When:
  77. - User-facing critical paths
  78. - Multi-system interactions
  79. - Regulatory compliance scenarios
  80. - Visual regression important
  81. ## Anti-patterns to Avoid
  82. - E2E testing for business logic validation
  83. - Unit testing framework behavior
  84. - Integration testing third-party libraries
  85. - Duplicate coverage across levels
  86. ## Duplicate Coverage Guard
  87. **Before adding any test, check:**
  88. 1. Is this already tested at a lower level?
  89. 2. Can a unit test cover this instead of integration?
  90. 3. Can an integration test cover this instead of E2E?
  91. **Coverage overlap is only acceptable when:**
  92. - Testing different aspects (unit: logic, integration: interaction, e2e: user experience)
  93. - Critical paths requiring defense in depth
  94. - Regression prevention for previously broken functionality
  95. ## Test Naming Conventions
  96. - Unit: `test_{component}_{scenario}`
  97. - Integration: `test_{flow}_{interaction}`
  98. - E2E: `test_{journey}_{outcome}`
  99. ## Test ID Format
  100. `{EPIC}.{STORY}-{LEVEL}-{SEQ}`
  101. Examples:
  102. - `1.3-UNIT-001`
  103. - `1.3-INT-002`
  104. - `1.3-E2E-001`
  105. ## Real Code Examples
  106. ### Example 1: E2E Test (Full User Journey)
  107. **Scenario**: User logs in, navigates to dashboard, and places an order.
  108. ```typescript
  109. // tests/e2e/checkout-flow.spec.ts
  110. import { test, expect } from '@playwright/test';
  111. import { createUser, createProduct } from '../test-utils/factories';
  112. test.describe('Checkout Flow', () => {
  113. test('user can complete purchase with saved payment method', async ({ page, apiRequest }) => {
  114. // Setup: Seed data via API (fast!)
  115. const user = createUser({ email: 'buyer@example.com', hasSavedCard: true });
  116. const product = createProduct({ name: 'Widget', price: 29.99, stock: 10 });
  117. await apiRequest.post('/api/users', { data: user });
  118. await apiRequest.post('/api/products', { data: product });
  119. // Network-first: Intercept BEFORE action
  120. const loginPromise = page.waitForResponse('**/api/auth/login');
  121. const cartPromise = page.waitForResponse('**/api/cart');
  122. const orderPromise = page.waitForResponse('**/api/orders');
  123. // Step 1: Login
  124. await page.goto('/login');
  125. await page.fill('[data-testid="email"]', user.email);
  126. await page.fill('[data-testid="password"]', 'password123');
  127. await page.click('[data-testid="login-button"]');
  128. await loginPromise;
  129. // Assert: Dashboard visible
  130. await expect(page).toHaveURL('/dashboard');
  131. await expect(page.getByText(`Welcome, ${user.name}`)).toBeVisible();
  132. // Step 2: Add product to cart
  133. await page.goto(`/products/${product.id}`);
  134. await page.click('[data-testid="add-to-cart"]');
  135. await cartPromise;
  136. await expect(page.getByText('Added to cart')).toBeVisible();
  137. // Step 3: Checkout with saved payment
  138. await page.goto('/checkout');
  139. await expect(page.getByText('Visa ending in 1234')).toBeVisible(); // Saved card
  140. await page.click('[data-testid="use-saved-card"]');
  141. await page.click('[data-testid="place-order"]');
  142. await orderPromise;
  143. // Assert: Order confirmation
  144. await expect(page.getByText('Order Confirmed')).toBeVisible();
  145. await expect(page.getByText(/Order #\d+/)).toBeVisible();
  146. await expect(page.getByText('$29.99')).toBeVisible();
  147. });
  148. });
  149. ```
  150. **Key Points (E2E)**:
  151. - Tests complete user journey across multiple pages
  152. - API setup for data (fast), UI for assertions (user-centric)
  153. - Network-first interception to prevent flakiness
  154. - Validates critical revenue path end-to-end
  155. ### Example 2: Integration Test (API/Service Layer)
  156. **Scenario**: UserService creates user and assigns role via AuthRepository.
  157. ```typescript
  158. // tests/integration/user-service.spec.ts
  159. import { test, expect } from '@playwright/test';
  160. import { createUser } from '../test-utils/factories';
  161. test.describe('UserService Integration', () => {
  162. test('should create user with admin role via API', async ({ request }) => {
  163. const userData = createUser({ role: 'admin' });
  164. // Direct API call (no UI)
  165. const response = await request.post('/api/users', {
  166. data: userData,
  167. });
  168. expect(response.status()).toBe(201);
  169. const createdUser = await response.json();
  170. expect(createdUser.id).toBeTruthy();
  171. expect(createdUser.email).toBe(userData.email);
  172. expect(createdUser.role).toBe('admin');
  173. // Verify database state
  174. const getResponse = await request.get(`/api/users/${createdUser.id}`);
  175. expect(getResponse.status()).toBe(200);
  176. const fetchedUser = await getResponse.json();
  177. expect(fetchedUser.role).toBe('admin');
  178. expect(fetchedUser.permissions).toContain('user:delete');
  179. expect(fetchedUser.permissions).toContain('user:update');
  180. // Cleanup
  181. await request.delete(`/api/users/${createdUser.id}`);
  182. });
  183. test('should validate email uniqueness constraint', async ({ request }) => {
  184. const userData = createUser({ email: 'duplicate@example.com' });
  185. // Create first user
  186. const response1 = await request.post('/api/users', { data: userData });
  187. expect(response1.status()).toBe(201);
  188. const user1 = await response1.json();
  189. // Attempt duplicate email
  190. const response2 = await request.post('/api/users', { data: userData });
  191. expect(response2.status()).toBe(409); // Conflict
  192. const error = await response2.json();
  193. expect(error.message).toContain('Email already exists');
  194. // Cleanup
  195. await request.delete(`/api/users/${user1.id}`);
  196. });
  197. });
  198. ```
  199. **Key Points (Integration)**:
  200. - Tests service layer + database interaction
  201. - No UI involved—pure API validation
  202. - Business logic focus (role assignment, constraints)
  203. - Faster than E2E, more realistic than unit tests
  204. ### Example 3: Component Test (Isolated UI Component)
  205. **Scenario**: Test button component in isolation with props and user interactions.
  206. ```typescript
  207. // src/components/Button.cy.tsx (Cypress Component Test)
  208. import { Button } from './Button';
  209. describe('Button Component', () => {
  210. it('should render with correct label', () => {
  211. cy.mount(<Button label="Click Me" />);
  212. cy.contains('Click Me').should('be.visible');
  213. });
  214. it('should call onClick handler when clicked', () => {
  215. const onClickSpy = cy.stub().as('onClick');
  216. cy.mount(<Button label="Submit" onClick={onClickSpy} />);
  217. cy.get('button').click();
  218. cy.get('@onClick').should('have.been.calledOnce');
  219. });
  220. it('should be disabled when disabled prop is true', () => {
  221. cy.mount(<Button label="Disabled" disabled={true} />);
  222. cy.get('button').should('be.disabled');
  223. cy.get('button').should('have.attr', 'aria-disabled', 'true');
  224. });
  225. it('should show loading spinner when loading', () => {
  226. cy.mount(<Button label="Loading" loading={true} />);
  227. cy.get('[data-testid="spinner"]').should('be.visible');
  228. cy.get('button').should('be.disabled');
  229. });
  230. it('should apply variant styles correctly', () => {
  231. cy.mount(<Button label="Primary" variant="primary" />);
  232. cy.get('button').should('have.class', 'btn-primary');
  233. cy.mount(<Button label="Secondary" variant="secondary" />);
  234. cy.get('button').should('have.class', 'btn-secondary');
  235. });
  236. });
  237. // Playwright Component Test equivalent
  238. import { test, expect } from '@playwright/experimental-ct-react';
  239. import { Button } from './Button';
  240. test.describe('Button Component', () => {
  241. test('should call onClick handler when clicked', async ({ mount }) => {
  242. let clicked = false;
  243. const component = await mount(
  244. <Button label="Submit" onClick={() => { clicked = true; }} />
  245. );
  246. await component.getByRole('button').click();
  247. expect(clicked).toBe(true);
  248. });
  249. test('should be disabled when loading', async ({ mount }) => {
  250. const component = await mount(<Button label="Loading" loading={true} />);
  251. await expect(component.getByRole('button')).toBeDisabled();
  252. await expect(component.getByTestId('spinner')).toBeVisible();
  253. });
  254. });
  255. ```
  256. **Key Points (Component)**:
  257. - Tests UI component in isolation (no full app)
  258. - Props + user interactions + visual states
  259. - Faster than E2E, more realistic than unit tests for UI
  260. - Great for design system components
  261. ### Example 4: Unit Test (Pure Function)
  262. **Scenario**: Test pure business logic function without framework dependencies.
  263. ```typescript
  264. // src/utils/price-calculator.test.ts (Jest/Vitest)
  265. import { calculateDiscount, applyTaxes, calculateTotal } from './price-calculator';
  266. describe('PriceCalculator', () => {
  267. describe('calculateDiscount', () => {
  268. it('should apply percentage discount correctly', () => {
  269. const result = calculateDiscount(100, { type: 'percentage', value: 20 });
  270. expect(result).toBe(80);
  271. });
  272. it('should apply fixed amount discount correctly', () => {
  273. const result = calculateDiscount(100, { type: 'fixed', value: 15 });
  274. expect(result).toBe(85);
  275. });
  276. it('should not apply discount below zero', () => {
  277. const result = calculateDiscount(10, { type: 'fixed', value: 20 });
  278. expect(result).toBe(0);
  279. });
  280. it('should handle no discount', () => {
  281. const result = calculateDiscount(100, { type: 'none', value: 0 });
  282. expect(result).toBe(100);
  283. });
  284. });
  285. describe('applyTaxes', () => {
  286. it('should calculate tax correctly for US', () => {
  287. const result = applyTaxes(100, { country: 'US', rate: 0.08 });
  288. expect(result).toBe(108);
  289. });
  290. it('should calculate tax correctly for EU (VAT)', () => {
  291. const result = applyTaxes(100, { country: 'DE', rate: 0.19 });
  292. expect(result).toBe(119);
  293. });
  294. it('should handle zero tax rate', () => {
  295. const result = applyTaxes(100, { country: 'US', rate: 0 });
  296. expect(result).toBe(100);
  297. });
  298. });
  299. describe('calculateTotal', () => {
  300. it('should calculate total with discount and taxes', () => {
  301. const items = [
  302. { price: 50, quantity: 2 }, // 100
  303. { price: 30, quantity: 1 }, // 30
  304. ];
  305. const discount = { type: 'percentage', value: 10 }; // -13
  306. const tax = { country: 'US', rate: 0.08 }; // +9.36
  307. const result = calculateTotal(items, discount, tax);
  308. expect(result).toBeCloseTo(126.36, 2);
  309. });
  310. it('should handle empty items array', () => {
  311. const result = calculateTotal([], { type: 'none', value: 0 }, { country: 'US', rate: 0 });
  312. expect(result).toBe(0);
  313. });
  314. it('should calculate correctly without discount or tax', () => {
  315. const items = [{ price: 25, quantity: 4 }];
  316. const result = calculateTotal(items, { type: 'none', value: 0 }, { country: 'US', rate: 0 });
  317. expect(result).toBe(100);
  318. });
  319. });
  320. });
  321. ```
  322. **Key Points (Unit)**:
  323. - Pure function testing—no framework dependencies
  324. - Fast execution (milliseconds)
  325. - Edge case coverage (zero, negative, empty inputs)
  326. - High cyclomatic complexity handled at unit level
  327. ## When to Use Which Level
  328. | Scenario | Unit | Integration | E2E |
  329. | ---------------------- | ------------- | ----------------- | ------------- |
  330. | Pure business logic | ✅ Primary | ❌ Overkill | ❌ Overkill |
  331. | Database operations | ❌ Can't test | ✅ Primary | ❌ Overkill |
  332. | API contracts | ❌ Can't test | ✅ Primary | ⚠️ Supplement |
  333. | User journeys | ❌ Can't test | ❌ Can't test | ✅ Primary |
  334. | Component props/events | ✅ Partial | ⚠️ Component test | ❌ Overkill |
  335. | Visual regression | ❌ Can't test | ⚠️ Component test | ✅ Primary |
  336. | Error handling (logic) | ✅ Primary | ⚠️ Integration | ❌ Overkill |
  337. | Error handling (UI) | ❌ Partial | ⚠️ Component test | ✅ Primary |
  338. ## Anti-Pattern Examples
  339. **❌ BAD: E2E test for business logic**
  340. ```typescript
  341. // DON'T DO THIS
  342. test('calculate discount via UI', async ({ page }) => {
  343. await page.goto('/calculator');
  344. await page.fill('[data-testid="price"]', '100');
  345. await page.fill('[data-testid="discount"]', '20');
  346. await page.click('[data-testid="calculate"]');
  347. await expect(page.getByText('$80')).toBeVisible();
  348. });
  349. // Problem: Slow, brittle, tests logic that should be unit tested
  350. ```
  351. **✅ GOOD: Unit test for business logic**
  352. ```typescript
  353. test('calculate discount', () => {
  354. expect(calculateDiscount(100, 20)).toBe(80);
  355. });
  356. // Fast, reliable, isolated
  357. ```
  358. _Source: Murat Testing Philosophy (test pyramid), existing test-levels-framework.md structure._