Software Testing Best Practices ve Stratejileri

Yazılım 📖 3 dk okuma
#web#design#javascript

Yazılım testleri, kod kalitesi ve güvenilirliğin temelidir.

Test Piramidi

      /\
     /E2E\      ← Az (Yavaş, Pahalı)
    /------\
   /Integration\ ← Orta
  /-----------\
 /   Unit     \ ← Çok (Hızlı, Ucuz)
/-------------\

Unit Testing

Jest Example

// math.js
export function add(a, b) {
  return a + b;
}

export function divide(a, b) {
  if (b === 0) throw new Error('Division by zero');
  return a / b;
}

// math.test.js
import { add, divide } from './math';

describe('Math functions', () => {
  test('adds two numbers', () => {
    expect(add(2, 3)).toBe(5);
  });
  
  test('divides two numbers', () => {
    expect(divide(10, 2)).toBe(5);
  });
  
  test('throws error on division by zero', () => {
    expect(() => divide(10, 0)).toThrow('Division by zero');
  });
});

React Component Testing

// Button.jsx
export function Button({ onClick, children }) {
  return <button onClick={onClick}>{children}</button>;
}

// Button.test.jsx
import { render, fireEvent, screen } from '@testing-library/react';
import { Button } from './Button';

describe('Button', () => {
  test('renders button text', () => {
    render(<Button>Click me</Button>);
    expect(screen.getByText('Click me')).toBeInTheDocument();
  });
  
  test('calls onClick when clicked', () => {
    const handleClick = jest.fn();
    render(<Button onClick={handleClick}>Click</Button>);
    
    fireEvent.click(screen.getByText('Click'));
    expect(handleClick).toHaveBeenCalledTimes(1);
  });
});

Integration Testing

// api.test.js
import request from 'supertest';
import app from './app';
import { setupTestDb, teardownTestDb } from './test-utils';

beforeAll(async () => {
  await setupTestDb();
});

afterAll(async () => {
  await teardownTestDb();
});

describe('User API', () => {
  test('POST /users creates a new user', async () => {
    const response = await request(app)
      .post('/api/users')
      .send({
        name: 'John Doe',
        email: 'john@example.com'
      })
      .expect(201);
    
    expect(response.body).toHaveProperty('id');
    expect(response.body.name).toBe('John Doe');
  });
  
  test('GET /users returns all users', async () => {
    const response = await request(app)
      .get('/api/users')
      .expect(200);
    
    expect(Array.isArray(response.body)).toBe(true);
  });
});

E2E Testing

Playwright Example

// e2e/login.spec.js
import { test, expect } from '@playwright/test';

test.describe('Login Flow', () => {
  test('successful login', async ({ page }) => {
    await page.goto('http://localhost:3000/login');
    
    await page.fill('input[name="email"]', 'user@example.com');
    await page.fill('input[name="password"]', 'password123');
    await page.click('button[type="submit"]');
    
    await expect(page).toHaveURL('http://localhost:3000/dashboard');
    await expect(page.locator('.user-name')).toContainText('John Doe');
  });
  
  test('login with invalid credentials', async ({ page }) => {
    await page.goto('http://localhost:3000/login');
    
    await page.fill('input[name="email"]', 'wrong@example.com');
    await page.fill('input[name="password"]', 'wrongpass');
    await page.click('button[type="submit"]');
    
    await expect(page.locator('.error')).toContainText('Invalid credentials');
  });
});

Mocking

// userService.js
export async function fetchUser(id) {
  const response = await fetch(`/api/users/${id}`);
  return response.json();
}

// userService.test.js
import { fetchUser } from './userService';

global.fetch = jest.fn();

test('fetches user successfully', async () => {
  const mockUser = { id: 1, name: 'John' };
  
  fetch.mockResolvedValueOnce({
    json: async () => mockUser
  });
  
  const user = await fetchUser(1);
  
  expect(fetch).toHaveBeenCalledWith('/api/users/1');
  expect(user).toEqual(mockUser);
});

Test Coverage

{
  "scripts": {
    "test": "jest",
    "test:coverage": "jest --coverage"
  },
  "jest": {
    "coverageThreshold": {
      "global": {
        "branches": 80,
        "functions": 80,
        "lines": 80,
        "statements": 80
      }
    }
  }
}

TDD (Test-Driven Development)

// 1. Red - Test yaz (fail)
test('validates email format', () => {
  expect(validateEmail('invalid')).toBe(false);
  expect(validateEmail('test@example.com')).toBe(true);
});

// 2. Green - Minimal kod yaz (pass)
function validateEmail(email) {
  return email.includes('@');
}

// 3. Refactor - İyileştir
function validateEmail(email) {
  const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
  return regex.test(email);
}

Snapshot Testing

import renderer from 'react-test-renderer';
import { Card } from './Card';

test('Card component renders correctly', () => {
  const tree = renderer
    .create(<Card title="Test" description="Description" />)
    .toJSON();
  
  expect(tree).toMatchSnapshot();
});

Testlerle güvenilir yazılımlar geliştirin!