Automatyczne generowanie testów
- Generuj testy z analizy kodu
- Twórz scenariusze przypadków brzegowych
- Buduj kompleksowe dane testowe
- Inteligentnie mockuj zależności
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
Inteligentne pokrycie
Samoobsługowe testy
Wgląd w jakość
// Oryginalna funkcja do testowaniaexport 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ówAgent: "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:
# Wygeneruj testy dla całego modułuclaude "Przeanalizuj src/services/UserService.ts i wygeneruj:- Testy jednostkowe ze 100% pokryciem- Testy integracyjne dla operacji bazodanowych- Implementacje mocków dla wszystkich zależności- Fabryki danych testowych- Benchmarki wydajności- Pokrycie scenariuszy błędów"
# Claude tworzy:# - src/services/__tests__/UserService.test.ts# - src/services/__tests__/UserService.integration.test.ts# - src/services/__mocks__/Database.ts# - src/services/__mocks__/EmailService.ts# - src/services/__tests__/factories/user.factory.ts
// Kompleksowy zestaw testów wygenerowany przez AIimport { 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ściachimport * 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'); } ) ); });});
// AI konfiguruje i uruchamia testowanie mutacyjnemodule.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 snapshotdescribe('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 testoweclass 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ówclass 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 testyclass 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); } }}
// Dobrze: Skoncentrowane, czytelne testyit('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ówit('powinien rzucić ValidationError gdy email zawiera spacje', () => { expect(() => validateEmail('user @example.com')) .toThrow(ValidationError);});
// Dobrze: Testowanie zachowania, nie implementacjiit('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 implementacjiit('powinien wywołać metodę wewnętrzną', () => { service.publicMethod(); expect(service._privateMethod).toHaveBeenCalled(); // Nie testuj prywatnych});
// Źle: Wiele asercji bez jasnego celuit('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 konfiguracjait('powinien obsłużyć przypadek brzegowy', () => { // 50 linii konfiguracji... // Właściwy test to 2 linie});
Metryka | Cel | Wzbogacenie AI |
---|---|---|
Szybkość generowania testów | 15x szybciej niż manual | Prompty w języku naturalnym |
Osiągnięcie pokrycia | >95% automatycznie | AI identyfikuje luki |
Redukcja utrzymywania | 90% mniej czasu | Samonaprawiające się testy |
Wykrywanie błędów | 75% więcej przypadków brzegowych | Rozpoznawanie wzorców AI |
Satysfakcja programistów | >90% pozytywnego feedbacku | Zmniejszone obciążenie poznawcze |
Czas do rynku | 40% szybsze wydania | Szybsze cykle walidacji |
Quality gates pokrycia
Quality gates niezawodności
# AI generuje testy pytestimport pytestfrom unittest.mock import Mock, patchfrom user_service import UserServicefrom 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() ); }}