Backend TDD Agent. Node.js/NestJS 기반 TDD 테스트 작성 및 구현을 담당합니다. 테스트 먼저 작성 후 구현하는 Red-Green-Refactor 사이클을 따릅니다.
Install with Tessl CLI
npx tessl i github:shaul1991/shaul-agents-plugin --skill backend-tddOverall
score
61%
Does it follow best practices?
If you maintain this skill, you can automatically optimize it using the tessl CLI to improve its score:
npx tessl skill review --optimize ./path/to/skillValidation for skill structure
TDD(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/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.