Przejdź do głównej zawartości

Strategie testowania jednostkowego

Przekształć swoje praktyki testowania jednostkowego dzięki generowaniu testów wspomaganemu przez AI, inteligentnej analizie pokrycia i samoobsługowym zestawom testów. Naucz się, jak osiągnąć kompleksowe pokrycie przy jednoczesnej redukcji czasu tworzenia i utrzymywania testów nawet o 90%.

Testowanie jednostkowe ewoluowało z żmudnego procesu manualnego w inteligentną, zautomatyzowaną praktykę, która zapewnia jakość kodu na najbardziej granularnym poziomie. Dzięki wsparciu AI możesz:

Automatyczne generowanie testów

  • Generuj testy z analizy kodu
  • Twórz scenariusze przypadków brzegowych
  • Buduj kompleksowe dane testowe
  • Inteligentnie mockuj zależności

Inteligentne pokrycie

  • Identyfikuj nietestowane ścieżki kodu
  • Sugeruj brakujące przypadki testowe
  • Optymalizuj kolejność wykonywania testów
  • Przewiduj obszary podatne na błędy

Samoobsługowe testy

  • Auto-aktualizuj dla zmian kodu
  • Naprawiaj złamane asercje
  • Refaktoryzuj strukturę testów
  • Usuwaj redundantne testy

Wgląd w jakość

  • Analiza testowania mutacyjnego
  • Ocena skuteczności testów
  • Benchmarking wydajności
  • Metryki jakości kodu

Szybki start: Wygeneruj swój pierwszy zestaw testów AI

Dział zatytułowany „Szybki start: Wygeneruj swój pierwszy zestaw testów AI”
// Oryginalna funkcja do testowania
export class UserService {
constructor(private db: Database, private emailService: EmailService) {}
async createUser(userData: CreateUserDto): Promise<User> {
// Waliduj format email
if (!this.isValidEmail(userData.email)) {
throw new ValidationError('Nieprawidłowy format email');
}
// Sprawdź istniejącego użytkownika
const existing = await this.db.users.findOne({ email: userData.email });
if (existing) {
throw new ConflictError('Użytkownik już istnieje');
}
// Utwórz użytkownika
const user = await this.db.users.create({
...userData,
id: generateId(),
createdAt: new Date(),
status: 'pending'
});
// Wyślij email powitalny
await this.emailService.sendWelcome(user.email, user.name);
return user;
}
}
// Prompt AI do generowania testów
Agent: "Wygeneruj kompleksowe testy jednostkowe dla UserService.createUser:
- Przetestuj wszystkie ścieżki kodu
- Uwzględnij przypadki brzegowe i scenariusze błędów
- Mockuj wszystkie zależności
- Dodaj testy wydajności
- Uwzględnij fabryki danych testowych
- Użyj Jest i TypeScript"
// AI generuje kompletny zestaw testów:
// Kompleksowy zestaw testów wygenerowany przez AI
import { UserService } from '../UserService';
import { Database } from '../Database';
import { EmailService } from '../EmailService';
import { ValidationError, ConflictError } from '../errors';
import { createMockDatabase, createMockEmailService } from './mocks';
import { UserFactory } from './factories/user.factory';
describe('UserService', () => {
let userService: UserService;
let mockDb: jest.Mocked<Database>;
let mockEmailService: jest.Mocked<EmailService>;
beforeEach(() => {
// AI konfiguruje kompleksowe mocki
mockDb = createMockDatabase();
mockEmailService = createMockEmailService();
userService = new UserService(mockDb, mockEmailService);
// Resetuj wszystkie mocki
jest.clearAllMocks();
});
describe('createUser', () => {
describe('pomyślne tworzenie', () => {
it('powinien utworzyć użytkownika z prawidłowymi danymi', async () => {
// Arrange
const userData = UserFactory.build();
mockDb.users.findOne.mockResolvedValue(null);
mockDb.users.create.mockResolvedValue({
...userData,
id: 'generated-id',
createdAt: new Date(),
status: 'pending'
});
// Act
const result = await userService.createUser(userData);
// Assert
expect(result).toMatchObject({
...userData,
id: expect.any(String),
status: 'pending',
createdAt: expect.any(Date)
});
expect(mockDb.users.findOne).toHaveBeenCalledWith({
email: userData.email
});
expect(mockDb.users.create).toHaveBeenCalledWith(
expect.objectContaining(userData)
);
expect(mockEmailService.sendWelcome).toHaveBeenCalledWith(
userData.email,
userData.name
);
});
it('powinien generować unikalne ID dla wielu użytkowników', async () => {
// AI dodaje test unikalności ID
const users = await Promise.all(
Array(10).fill(null).map(() =>
userService.createUser(UserFactory.build())
)
);
const ids = users.map(u => u.id);
expect(new Set(ids).size).toBe(10);
});
});
describe('błędy walidacji', () => {
it.each([
['invalid-email', 'Nieprawidłowy format email'],
['@example.com', 'Nieprawidłowy format email'],
['user@', 'Nieprawidłowy format email'],
['user..name@example.com', 'Nieprawidłowy format email'],
])('powinien odrzucić nieprawidłowy email: %s', async (email, expectedError) => {
const userData = UserFactory.build({ email });
await expect(userService.createUser(userData))
.rejects.toThrow(ValidationError);
await expect(userService.createUser(userData))
.rejects.toThrow(expectedError);
expect(mockDb.users.findOne).not.toHaveBeenCalled();
expect(mockDb.users.create).not.toHaveBeenCalled();
expect(mockEmailService.sendWelcome).not.toHaveBeenCalled();
});
});
describe('obsługa duplikatów użytkowników', () => {
it('powinien rzucić ConflictError dla istniejącego email', async () => {
const existingUser = UserFactory.build();
mockDb.users.findOne.mockResolvedValue(existingUser);
const newUserData = UserFactory.build({
email: existingUser.email
});
await expect(userService.createUser(newUserData))
.rejects.toThrow(ConflictError);
await expect(userService.createUser(newUserData))
.rejects.toThrow('Użytkownik już istnieje');
expect(mockDb.users.create).not.toHaveBeenCalled();
expect(mockEmailService.sendWelcome).not.toHaveBeenCalled();
});
});
describe('obsługa błędów', () => {
it('powinien elegancko obsłużyć błędy bazy danych', async () => {
mockDb.users.findOne.mockRejectedValue(
new Error('Błąd połączenia z bazą danych')
);
await expect(userService.createUser(UserFactory.build()))
.rejects.toThrow('Błąd połączenia z bazą danych');
});
it('nie powinien wysyłać email jeśli tworzenie użytkownika się nie powiedzie', async () => {
mockDb.users.findOne.mockResolvedValue(null);
mockDb.users.create.mockRejectedValue(
new Error('Tworzenie nie powiodło się')
);
await expect(userService.createUser(UserFactory.build()))
.rejects.toThrow('Tworzenie nie powiodło się');
expect(mockEmailService.sendWelcome).not.toHaveBeenCalled();
});
it('powinien obsłużyć błędy serwisu email', async () => {
mockDb.users.findOne.mockResolvedValue(null);
const createdUser = UserFactory.build();
mockDb.users.create.mockResolvedValue(createdUser);
mockEmailService.sendWelcome.mockRejectedValue(
new Error('Serwis email niedostępny')
);
// Użytkownik powinien zostać utworzony nawet jeśli email się nie powiedzie
const result = await userService.createUser(UserFactory.build());
expect(result).toBeDefined();
});
});
describe('wydajność', () => {
it('powinien utworzyć użytkownika w akceptowalnym czasie', async () => {
const startTime = Date.now();
await userService.createUser(UserFactory.build());
const duration = Date.now() - startTime;
expect(duration).toBeLessThan(100); // Próg 100ms
});
it('powinien obsłużyć równoczesne tworzenie użytkowników', async () => {
const userData = Array(100).fill(null).map(() =>
UserFactory.build()
);
const results = await Promise.all(
userData.map(data => userService.createUser(data))
);
expect(results).toHaveLength(100);
expect(mockDb.users.create).toHaveBeenCalledTimes(100);
});
});
});
});
// AI generuje testy oparte na właściwościach
import * as fc from 'fast-check';
describe('UserService - Testy właściwości', () => {
it('powinien zawsze tworzyć prawidłowych użytkowników niezależnie od wejścia', () => {
fc.assert(
fc.property(
// AI generuje kompleksowe arbitraries
fc.record({
name: fc.string({ minLength: 1, maxLength: 100 }),
email: fc.emailAddress(),
age: fc.integer({ min: 0, max: 150 }),
preferences: fc.dictionary(fc.string(), fc.jsonValue())
}),
async (userData) => {
const result = await userService.createUser(userData);
// Właściwości, które powinny zawsze być prawdziwe
expect(result.id).toBeDefined();
expect(result.createdAt).toBeInstanceOf(Date);
expect(result.email).toBe(userData.email.toLowerCase());
expect(result.status).toBe('pending');
}
)
);
});
});
stryker.conf.js
// AI konfiguruje i uruchamia testowanie mutacyjne
module.exports = {
mutator: 'typescript',
packageManager: 'npm',
reporters: ['html', 'clear-text', 'progress'],
testRunner: 'jest',
transpilers: ['typescript'],
coverageAnalysis: 'perTest',
mutate: ['src/**/*.ts', '!src/**/*.test.ts'],
// Testowanie mutacyjne zoptymalizowane przez AI
thresholds: {
high: 90,
low: 80,
break: 75
},
// AI sugeruje które mutacje priorytetyzować
mutationLevels: [
'ConditionalExpression',
'LogicalOperator',
'StringLiteral',
'BooleanLiteral'
]
};
// AI wzbogaca testowanie snapshot
describe('Renderowanie komponentu', () => {
it('powinien pasować do snapshot z normalizacją dynamicznych danych', () => {
const component = render(<UserProfile user={testUser} />);
// AI normalizuje wartości dynamiczne
const snapshot = normalizeSnapshot(component.asFragment(), {
timestamps: 'TIMESTAMP',
ids: 'ID',
randomValues: 'RANDOM'
});
expect(snapshot).toMatchSnapshot();
});
// AI wykrywa znaczące vs trywialne zmiany snapshot
it('powinien alertować tylko o istotnych zmianach', () => {
const aiAnalysis = analyzeSnapshotDiff(
previousSnapshot,
currentSnapshot
);
if (aiAnalysis.isSignificant) {
console.warn('Wykryto istotną zmianę UI:', aiAnalysis.summary);
}
});
});
// AI generuje realistyczne dane testowe
class UserFactory {
static build(overrides?: Partial<User>): User {
return {
id: faker.datatype.uuid(),
email: faker.internet.email().toLowerCase(),
name: faker.name.fullName(),
age: faker.datatype.number({ min: 18, max: 80 }),
address: {
street: faker.address.streetAddress(),
city: faker.address.city(),
country: faker.address.country(),
zipCode: faker.address.zipCode()
},
preferences: this.generateRealisticPreferences(),
createdAt: faker.date.recent(),
...overrides
};
}
static generateRealisticPreferences() {
// AI tworzy realistyczne preferencje użytkowników
return {
notifications: {
email: faker.datatype.boolean({ probability: 0.7 }),
sms: faker.datatype.boolean({ probability: 0.3 }),
push: faker.datatype.boolean({ probability: 0.5 })
},
theme: faker.helpers.arrayElement(['light', 'dark', 'auto']),
language: faker.helpers.arrayElement(['en', 'es', 'fr', 'de']),
timezone: faker.address.timeZone()
};
}
static buildList(count: number): User[] {
// AI zapewnia różnorodne dane testowe
return Array(count).fill(null).map((_, index) =>
this.build({
// Zapewnij pewną różnorodność w danych testowych
age: 18 + (index % 62),
preferences: index % 3 === 0 ?
{ notifications: { email: true } } :
undefined
})
);
}
}
// AI analizuje i usprawnia pokrycie testów
class CoverageOptimizer {
async analyzeCoverage(testResults: TestResults): Promise<CoverageReport> {
const uncoveredPaths = this.findUncoveredPaths(testResults);
const suggestions = await this.ai.generateTestSuggestions(uncoveredPaths);
return {
currentCoverage: testResults.coverage,
uncoveredPaths,
suggestions,
prioritizedTests: this.prioritizeByRisk(suggestions),
estimatedImprovement: this.calculatePotentialCoverage(suggestions)
};
}
generateMissingTests(report: CoverageReport): TestCase[] {
return report.suggestions.map(suggestion => ({
description: suggestion.description,
code: this.ai.generateTestCode(suggestion),
priority: suggestion.riskScore,
estimatedCoverage: suggestion.coverageGain
}));
}
}
// AI automatycznie utrzymuje testy
class TestMaintainer {
async fixBrokenTest(test: TestCase, error: TestError): Promise<TestCase> {
const diagnosis = await this.ai.diagnoseFailure(test, error);
switch (diagnosis.type) {
case 'ASSERTION_OUTDATED':
return this.updateAssertions(test, diagnosis.newExpectations);
case 'MOCK_MISMATCH':
return this.updateMocks(test, diagnosis.actualCalls);
case 'SCHEMA_CHANGE':
return this.adaptToSchemaChange(test, diagnosis.schemaUpdate);
case 'TIMING_ISSUE':
return this.addProperWaits(test, diagnosis.timingAnalysis);
default:
return this.flagForManualReview(test, diagnosis);
}
}
}

Najlepsze praktyki testowania jednostkowego wspomaganego przez AI

Dział zatytułowany „Najlepsze praktyki testowania jednostkowego wspomaganego przez AI”
  1. Zacznij od krytycznych ścieżek - Generuj testy dla podstawowej logiki biznesowej najpierw
  2. Przeglądaj wyniki AI - Zawsze sprawdzaj wygenerowane testy pod kątem poprawności
  3. Utrzymuj jakość testów - Zapewnij, że testy są czytelne i utrzymywalne
  4. Używaj odpowiednich mocków - Nie over-mockuj; testuj prawdziwe integracje tam gdzie wartościowe
  5. Monitoruj wydajność testów - Utrzymuj szybkie testy (< 100ms na test)
  6. Aktualizuj regularnie - Pozwól AI aktualizować testy w miarę ewolucji kodu
  7. Mierz skuteczność - Śledź wynik mutacji, nie tylko pokrycie
// Dobrze: Skoncentrowane, czytelne testy
it('powinien poprawnie obliczyć zniżkę dla użytkowników premium', () => {
const user = UserFactory.build({ tier: 'premium' });
const discount = calculateDiscount(user, 100);
expect(discount).toBe(20); // 20% dla premium
});
// Dobrze: Opisowe nazwy testów
it('powinien rzucić ValidationError gdy email zawiera spacje', () => {
expect(() => validateEmail('user @example.com'))
.toThrow(ValidationError);
});
// Dobrze: Testowanie zachowania, nie implementacji
it('powinien powiadomić użytkownika po pomyślnym zakupie', async () => {
await purchaseService.completePurchase(order);
expect(notificationService.send).toHaveBeenCalledWith(
expect.objectContaining({ type: 'purchase_complete' })
);
});
// Źle: Testowanie szczegółów implementacji
it('powinien wywołać metodę wewnętrzną', () => {
service.publicMethod();
expect(service._privateMethod).toHaveBeenCalled(); // Nie testuj prywatnych
});
// Źle: Wiele asercji bez jasnego celu
it('powinien działać', () => {
const result = service.process(data);
expect(result).toBeDefined();
expect(result.id).toBeTruthy();
expect(result.data).toEqual(expect.anything());
// Co właściwie testujemy?
});
// Źle: Zbyt skomplikowana konfiguracja
it('powinien obsłużyć przypadek brzegowy', () => {
// 50 linii konfiguracji...
// Właściwy test to 2 linie
});
MetrykaCelWzbogacenie AI
Szybkość generowania testów15x szybciej niż manualPrompty w języku naturalnym
Osiągnięcie pokrycia>95% automatycznieAI identyfikuje luki
Redukcja utrzymywania90% mniej czasuSamonaprawiające się testy
Wykrywanie błędów75% więcej przypadków brzegowychRozpoznawanie wzorców AI
Satysfakcja programistów>90% pozytywnego feedbackuZmniejszone obciążenie poznawcze
Czas do rynku40% szybsze wydaniaSzybsze cykle walidacji

Quality gates pokrycia

  • Pokrycie linii: >90%
  • Pokrycie gałęzi: >85%
  • Wynik mutacji: >80%
  • Wydajność: <100ms śr

Quality gates niezawodności

  • Flakiness testów: <1%
  • Fałszywe pozytywny: <2%
  • Czas utrzymywania: <5% miesięcznie
  • Integracja CI/CD: 100%
# AI generuje testy pytest
import pytest
from unittest.mock import Mock, patch
from user_service import UserService
from factories import UserFactory
class TestUserService:
@pytest.fixture
def service(self):
"""AI tworzy kompleksowe fixtures"""
db = Mock()
email_service = Mock()
return UserService(db, email_service)
@pytest.mark.parametrize("email,expected", [
("valid@example.com", True),
("invalid.email", False),
("@example.com", False),
("user@", False),
])
def test_email_validation(self, service, email, expected):
"""AI generuje testy parametryzowane"""
assert service.is_valid_email(email) == expected
@pytest.mark.asyncio
async def test_create_user_success(self, service):
"""AI obsługuje testowanie async"""
user_data = UserFactory.build()
service.db.find_one.return_value = None
service.db.create.return_value = user_data
result = await service.create_user(user_data)
assert result.email == user_data["email"]
service.email_service.send_welcome.assert_called_once()
// AI generuje testy JUnit 5
@ExtendWith(MockitoExtension.class)
class UserServiceTest {
@Mock private Database database;
@Mock private EmailService emailService;
@InjectMocks private UserService userService;
@ParameterizedTest
@ValueSource(strings = {
"invalid-email",
"@example.com",
"user@",
"user..name@example.com"
})
void shouldRejectInvalidEmails(String email) {
// AI generuje kompleksowe testy parametryzowane
var userData = UserFactory.build()
.withEmail(email);
assertThrows(
ValidationException.class,
() -> userService.createUser(userData)
);
verifyNoInteractions(database, emailService);
}
@Test
@DisplayName("Powinien utworzyć użytkownika z prawidłowymi danymi")
void createUser_withValidData_success() {
// AI tworzy czytelne, dobrze ustrukturyzowane testy
var userData = UserFactory.build();
when(database.findByEmail(userData.getEmail()))
.thenReturn(Optional.empty());
when(database.save(any(User.class)))
.thenReturn(userData);
var result = userService.createUser(userData);
assertAll(
() -> assertNotNull(result.getId()),
() -> assertEquals("pending", result.getStatus()),
() -> assertNotNull(result.getCreatedAt())
);
verify(emailService).sendWelcome(
userData.getEmail(),
userData.getName()
);
}
}