Backend TDD Agent. Node.js/NestJS 기반 TDD 테스트 작성 및 구현을 담당합니다. 테스트 먼저 작성 후 구현하는 Red-Green-Refactor 사이클을 따릅니다.
48
48%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
Passed
No findings from the security scan
Fix and improve this skill with Tessl
tessl review fix ./skills/backend-tdd/SKILL.mdTDD(Test-Driven Development) 방식으로 Backend 코드를 개발합니다. 테스트를 먼저 작성하고, 테스트를 통과하는 최소한의 코드를 구현합니다.
┌─────────────────────────────────────────────────────────────────┐
│ 1. RED (실패하는 테스트) │
│ - 테스트 케이스 작성 │
│ - 테스트 실행 → 실패 확인 │
└─────────────────────────────┬───────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ 2. GREEN (테스트 통과) │
│ - 최소한의 코드 작성 │
│ - 테스트 실행 → 통과 확인 │
└─────────────────────────────┬───────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ 3. REFACTOR (리팩토링) │
│ - 코드 개선 │
│ - 테스트 실행 → 여전히 통과 확인 │
└─────────────────────────────────────────────────────────────────┘import { Test, TestingModule } from '@nestjs/testing';
import { UserService } from './user.service';
import { getRepositoryToken } from '@nestjs/typeorm';
import { User } from './entities/user.entity';
import { NotFoundException } from '@nestjs/common';
describe('UserService', () => {
let service: UserService;
let mockRepository: any;
beforeEach(async () => {
mockRepository = {
findOne: jest.fn(),
find: jest.fn(),
save: jest.fn(),
delete: jest.fn(),
};
const module: TestingModule = await Test.createTestingModule({
providers: [
UserService,
{
provide: getRepositoryToken(User),
useValue: mockRepository,
},
],
}).compile();
service = module.get<UserService>(UserService);
});
describe('findById', () => {
it('should return user when found', async () => {
// Arrange
const user = { id: 1, name: 'John', email: 'john@test.com' };
mockRepository.findOne.mockResolvedValue(user);
// Act
const result = await service.findById(1);
// Assert
expect(result).toEqual(user);
expect(mockRepository.findOne).toHaveBeenCalledWith({ where: { id: 1 } });
});
it('should throw NotFoundException when user not found', async () => {
// Arrange
mockRepository.findOne.mockResolvedValue(null);
// Act & Assert
await expect(service.findById(999)).rejects.toThrow(NotFoundException);
});
});
describe('create', () => {
it('should create and return new user', async () => {
// Arrange
const createDto = { name: 'John', email: 'john@test.com' };
const savedUser = { id: 1, ...createDto };
mockRepository.save.mockResolvedValue(savedUser);
// Act
const result = await service.create(createDto);
// Assert
expect(result).toEqual(savedUser);
expect(mockRepository.save).toHaveBeenCalledWith(createDto);
});
});
});import { Test, TestingModule } from '@nestjs/testing';
import { UserController } from './user.controller';
import { UserService } from './user.service';
describe('UserController', () => {
let controller: UserController;
let mockService: any;
beforeEach(async () => {
mockService = {
findById: jest.fn(),
findAll: jest.fn(),
create: jest.fn(),
update: jest.fn(),
delete: jest.fn(),
};
const module: TestingModule = await Test.createTestingModule({
controllers: [UserController],
providers: [
{
provide: UserService,
useValue: mockService,
},
],
}).compile();
controller = module.get<UserController>(UserController);
});
describe('findOne', () => {
it('should return user by id', async () => {
// Arrange
const user = { id: 1, name: 'John' };
mockService.findById.mockResolvedValue(user);
// Act
const result = await controller.findOne(1);
// Assert
expect(result).toEqual(user);
});
});
});import { Test, TestingModule } from '@nestjs/testing';
import { INestApplication } from '@nestjs/common';
import * as request from 'supertest';
import { AppModule } from '../src/app.module';
describe('UserController (e2e)', () => {
let app: INestApplication;
beforeEach(async () => {
const moduleFixture: TestingModule = await Test.createTestingModule({
imports: [AppModule],
}).compile();
app = moduleFixture.createNestApplication();
await app.init();
});
afterEach(async () => {
await app.close();
});
describe('/users (GET)', () => {
it('should return all users', () => {
return request(app.getHttpServer())
.get('/users')
.expect(200)
.expect((res) => {
expect(Array.isArray(res.body)).toBe(true);
});
});
});
describe('/users (POST)', () => {
it('should create new user', () => {
return request(app.getHttpServer())
.post('/users')
.send({ name: 'John', email: 'john@test.com' })
.expect(201)
.expect((res) => {
expect(res.body.name).toBe('John');
expect(res.body.id).toBeDefined();
});
});
it('should return 400 when invalid data', () => {
return request(app.getHttpServer())
.post('/users')
.send({ name: '' })
.expect(400);
});
});
describe('/users/:id (GET)', () => {
it('should return 404 when user not found', () => {
return request(app.getHttpServer())
.get('/users/99999')
.expect(404);
});
});
});# 전체 테스트 실행
npm run test
# Watch 모드
npm run test:watch
# 특정 파일 테스트
npm run test -- user.service
# 커버리지 리포트
npm run test:cov
# E2E 테스트
npm run test:e2eexport const repositoryMockFactory = <T = any>(): MockType<Repository<T>> => ({
findOne: jest.fn(),
find: jest.fn(),
save: jest.fn(),
delete: jest.fn(),
createQueryBuilder: jest.fn(() => ({
where: jest.fn().mockReturnThis(),
andWhere: jest.fn().mockReturnThis(),
getMany: jest.fn(),
getOne: jest.fn(),
})),
});
export type MockType<T> = {
[P in keyof T]?: jest.Mock<any>;
};// test/setup.ts
import { TypeOrmModule } from '@nestjs/typeorm';
export const TestDatabaseModule = TypeOrmModule.forRoot({
type: 'sqlite',
database: ':memory:',
entities: ['src/**/*.entity.ts'],
synchronize: true,
});| 유형 | 목표 |
|---|---|
| 전체 | > 80% |
| Service | > 90% |
| Controller | > 70% |
| Utils | > 95% |
src/**/*.spec.tstest/**/*.e2e-spec.tstest/utils/test/fixtures/9242c58
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.