-
Notifications
You must be signed in to change notification settings - Fork 1
[재하] 1115(수) 개발기록
다음 이슈를 분석해 요구사항을 구체화하고 e2e 테스트로 만들자
- #12 [02-05] 서버는 아이디 중복을 검사하고 결과를 클라이언트에 전송한다.
- #16 [02-09] 서버는 회원가입 데이터를 받아 형식 검사와 아이디 중복검사를 진행한다.
- #17 [02-10] 검사에 통과하면 회원 정보를 데이터베이스에 저장한다.
- #45 [06-08] 서버는 좋아요 / 좋아요 취소 요청을 받아 데이터베이스의 데이터를 수정한다.
- #39 [06-02] 서버는 사용자의 글 데이터를 전송한다.
- #60 [08-06] 서버는 전송 받은 데이터를 데이터베이스에 저장한다.
- #65 [09-03] 서버는 검색된 사용자의 글 데이터를 전송한다.
- 각 이슈에 요구되는 Entity의 최소 컬럼을 식별하고 구현하자
- SWAGGER로 API 명세 자동화!
- 이슈 분석
- board e2e, 실패하는 테스트 코드 작성 계획
- POST /board 실패하는 테스트 작성, 성공하도록 구현
- GET /board/:id (RED, GREEN, REFACTOR)
- #12 [02-05] 서버는 아이디 중복을 검사하고 결과를 클라이언트에 전송한다.
- 회원 가입 전 아이디 중복 검사 버튼에 대한 처리
- GET /auth/check-available-username
- Request Parameters
- username: string
- Response JSON Properties
- success: boolean
- message: string
- #16 [02-09] 서버는 회원가입 데이터를 받아 형식 검사와 아이디 중복검사를 진행한다.
- #17 [02-10] 검사에 통과하면 회원 정보를 데이터베이스에 저장한다.
- 회원가입 요청 처리. 하나의 API에서 validate 및 DB 저장 (#16, #17)
- POST /auth/register
- Request JSON Properties
- username: string
- password: string
- nickname: string
- Response JSON Properties
- success: boolean
- message: string
- #45 [06-08] 서버는 좋아요 / 좋아요 취소 요청을 받아 데이터베이스의 데이터를 수정한다.
- 좋아요 업데이트. like, unlike 두 개의 API로 처리
- PUT /board/:id/like
- PUT /board/:id/unlike
- Request Parameters
- username: string
- Response JSON Properties
- like_cnt: number
- #39 [06-02] 서버는 사용자의 글 데이터를 전송한다.
- board id로 글 검색하여 전송 (findOne)
- GET /board/:id
- Response
- board: Board
- (추가 필요) 서버는 사용자의 글 목록을 전송한다.
- user로 필터링하여 글 목록 전송 (findAllBy)
- GET /board
- Response
- board: Board[]
- #60 [08-06] 서버는 전송 받은 데이터를 데이터베이스에 저장한다.
- 작성한 글을 데이터베이스에 저장 (create, save)
- POST /board
- Request JSON Properties
- title: string
- content: string
- author: string
- Response
- board: Board
- #65 [09-03] 서버는 검색된 사용자의 글 데이터를 전송한다.
- 사용자 username으로 글 리스트 필터하여 전송 (findAllBy)
- GET /board/boards-by-author
- Request Parameters
- author: string
- Response
- boards: Board[]
- (추가 필요) 서버는 사용자의 요청에 따라 글을 수정한다.
- board id에 해당하는 글 수정
- PUT /board/:id
- Request Parameters
- title: string
- content: string
- author: string
- Response
- board: Board
- (추가 필요) 서버는 사용자의 요청에 따라 글을 삭제한다.
- board id에 해당하는 글 수정
- DELETE /board/:id
- Response JSON Properties
- success: boolean
학습메모 4를 참고하여, Red-Green-Refactor 방식을 준용해 보았다.
- Red (실패하는 테스트 코드 작성)
- Green (테스트를 통과하도록 구현)
- Refactor (리팩토링)
board가 단순 CRUD에 더 가까우므로 먼저 해보기로 했다.
test/board
에 board.e2e-spec.ts
파일 생성
import { Test, TestingModule } from '@nestjs/testing';
import { INestApplication } from '@nestjs/common';
import * as request from 'supertest';
import { AppModule } from '../../src/app.module';
describe('BoardController (e2e)', () => {
let app: INestApplication;
beforeEach(async () => {
const moduleFixture: TestingModule = await Test.createTestingModule({
imports: [AppModule],
}).compile();
app = moduleFixture.createNestApplication();
await app.init();
});
describe('/board', () => {
// #39 [06-02] 서버는 사용자의 글 데이터를 전송한다.
it.todo('GET /board/:id');
// (추가 필요) 서버는 사용자의 글 목록을 전송한다.
it.todo('GET /board');
// #45 [06-08] 서버는 좋아요 / 좋아요 취소 요청을 받아 데이터베이스의 데이터를 수정한다.
it.todo('PUT /board/:id/like');
it.todo('PUT /board/:id/unlike');
// #60 [08-06] 서버는 전송 받은 데이터를 데이터베이스에 저장한다.
it.todo('POST /board');
// #65 [09-03] 서버는 검색된 사용자의 글 데이터를 전송한다.
it.todo('GET /board/by-author');
// (추가 필요) 서버는 사용자의 요청에 따라 글을 수정한다.
it.todo('PUT /board/:id');
// (추가 필요) 서버는 사용자의 요청에 따라 글을 삭제한다.
it.todo('DELETE /board/:id');
});
});
todo로 우선 요구사항 및 API 명세를 등록했다.
POST /board
에 대해 테스트 코드를 작성해보자.
// #60 [08-06] 서버는 전송 받은 데이터를 데이터베이스에 저장한다.
it('POST /board', () => {
const board = {
title: 'test',
content: 'test',
author: 'test',
};
return request(app.getHttpServer())
.post('/board')
.send(board)
.expect(201, {
id: 1,
...board,
});
});
yarn workspace server test '.../board.e2e-spec.ts'
재밌는게 Nest에서 201 응답은 이미 처리해줘버림ㅋ 이제 여기를 통과하게 Entity랑 DTO 만들고 Controller, Service 코드를 수정해보자.
// board.entity.ts
import { BaseEntity, Column, Entity, PrimaryGeneratedColumn } from 'typeorm';
@Entity()
export class Board extends BaseEntity {
@PrimaryGeneratedColumn()
id: number;
@Column({ type: 'varchar', length: 255, nullable: false })
title: string;
@Column({ type: 'text', nullable: true })
content: string;
@Column({ type: 'varchar', length: 50, nullable: false })
author: string;
@Column({ type: 'timestamp', default: () => 'CURRENT_TIMESTAMP' })
created_at: Date;
@Column({ type: 'timestamp', default: () => 'CURRENT_TIMESTAMP' })
updated_at: Date;
}
// create-board.dto.ts
export class CreateBoardDto {
title: string;
content: string;
author: string;
}
이제 컨트롤러, 서비스를 수정해주자. DB까지 연동!
// board.controller.ts
@Controller('board')
export class BoardController {
constructor(private readonly boardService: BoardService) {}
@Post()
create(@Body() createBoardDto: CreateBoardDto): Promise<Board> {
return this.boardService.create(createBoardDto);
}
...
}
// board.service.ts
...
@Injectable()
export class BoardService {
constructor(
@InjectRepository(Board)
private boardRepository: Repository<Board>,
) {}
async create(createBoardDto: CreateBoardDto): Promise<Board> {
const { title, content, author } = createBoardDto;
const board = await this.boardRepository.create({
title,
content,
author,
});
const created: Board = await this.boardRepository.save(board);
return created;
}
...
}
repository도 주입해주고, 생성한 Board 인스턴스를 리턴하도록 해 id값을 얻을 수 있게 해줬다.
여기까지 테스트하니 id를 1으로 고정시켜놓은 것도 그렇고, created_at 등 추가된 컬럼과 앞으로 추가될 컬럼까지 고려해서 수정하지 않아도 되는 테스트를 만들고 싶었다.
// #60 [08-06] 서버는 전송 받은 데이터를 데이터베이스에 저장한다.
it('POST /board', async () => {
const board = {
title: 'test',
content: 'test',
author: 'test',
};
const response = await request(app.getHttpServer())
.post('/board')
.send(board)
.expect(201);
expect(response).toHaveProperty('body');
expect((response as any).body).toMatchObject(board);
expect((response as any).body).toHaveProperty('id');
expect(typeof response.body.id).toBe('number');
});
그래서 위와 같이 테스트코드를 수정함.
아름답게 통과된다! 리팩토링은 따로 필요없을 것 같아 생략.
이후부터는 방법론 명칭대로 다음과 같이 간략히 기재하겠음.
- RED : 실패하는 테스트코드 작성
- GREEN : 테스트 통과하도록 구현
- REFACTOR : 리팩토링
// board.e2e-spec.ts
// #39 [06-02] 서버는 사용자의 글 데이터를 전송한다.
it('GET /board/:id', async () => {
const response = await request(app.getHttpServer())
.get('/board/1')
.expect(200);
expect(response).toHaveProperty('body');
expect((response as any).body).toHaveProperty('id');
expect(response.body.id).toBe(1);
expect((response as any).body).toHaveProperty('title');
expect((response as any).body).toHaveProperty('content');
expect((response as any).body).toHaveProperty('author');
expect((response as any).body).toHaveProperty('created_at');
expect((response as any).body).toHaveProperty('updated_at');
});
실패하는 테스트 코드를 작성해준다. id가 일치해야 하며, 다음 기대되는 속성들을 받아올 수 있어야 함: id, title, content, author, created_at, updated_at
// board.service.ts
async findOne(id: number) {
const found: Board = await this.boardRepository.findOneBy({ id });
return found;
}
서비스 메소드인 findOne()을 위와 같이 수정해주면 된다.
예외처리와 적절한 함수이름으로의 변경, 타입 명시 등을 추가로 처리해줬다.
참고로 입력값에 대한 유효성 검증 등은 기본기능 구현 후 추가할 예정.
// board.controller.ts
@Get(':id')
getBoardById(@Param('id') id: string): Promise<Board> {
return this.boardService.getBoardById(+id);
}
// board.service.ts
async getBoardById(id: number): Promise<Board> {
const found: Board = await this.boardRepository.findOneBy({ id });
if (!found) {
throw new NotFoundException(`Not found board with id: ${id}`);
}
return found;
}
통과는 마찬가지로 잘 된다.
Not Found Exception 처리도 확인 (이것도 추후 테스트 추가해주면 좋을 듯 하다)
© 2023 debussysanjang
- 🐙 [가은] Three.js와의 설레는 첫만남
- 🐙 [가은] JS로 자전과 공전을 구현할 수 있다고?
- ⚽️ [준섭] NestJS 강의 정리본
- 🐧 [동민] R3F Material 간단 정리
- 👾 [재하] 만들면서 배우는 NestJS 기초
- 👾 [재하] GitHub Actions을 이용한 자동 배포
- ⚽️ [준섭] 테스트 코드 작성 이유
- ⚽️ [준섭] TypeScript의 type? interface?
- 🐙 [가은] 우리 팀이 Zustand를 쓰는 이유
- 👾 [재하] NestJS, TDD로 개발하기
- 👾 [재하] AWS와 NCP의 주요 서비스
- 🐰 [백범] Emotion 선택시 고려사항
- 🐧 [동민] Yarn berry로 모노레포 구성하기
- 🐧 [동민] Vite, 왜 쓰는거지?
- ⚽️ [준섭] 동시성 제어
- 👾 [재하] NestJS에 Swagger 적용하기
- 🐙 [가은] 너와의 추억을 우주의 별로 띄울게
- 🐧 [동민] React로 멋진 3D 은하 만들기(feat. R3F)
- ⚽️ [준섭] NGINX 설정
- 👾 [재하] Transaction (트랜잭션)
- 👾 [재하] SSH 보안: Key Forwarding, Tunneling, 포트 변경
- ⚽️ [준섭] MySQL의 검색 - LIKE, FULLTEXT SEARCH(전문검색)
- 👾 [재하] Kubernetes 기초(minikube), docker image 최적화(멀티스테이징)
- 👾 [재하] NestJS, 유닛 테스트 각종 mocking, e2e 테스트 폼데이터 및 파일첨부
- 2주차(화) - git, monorepo, yarn berry, TDD
- 2주차(수) - TDD, e2e 테스트
- 2주차(목) - git merge, TDD
- 2주차(일) - NCP 배포환경 구성, MySQL, nginx, docker, docker-compose
- 3주차(화) - Redis, Multer 파일 업로드, Validation
- 3주차(수) - AES 암복호화, TypeORM Entity Relation
- 3주차(목) - NCP Object Storage, HTTPS, GitHub Actions
- 3주차(토) - Sharp(이미지 최적화)
- 3주차(일) - MongoDB
- 4주차(화) - 플랫폼 종속성 문제 해결(Sharp), 쿼리 최적화
- 4주차(수) - 코드 개선, 트랜잭션 제어
- 4주차(목) - 트랜잭션 제어
- 4주차(일) - docker 이미지 최적화
- 5주차(화) - 어드민 페이지(전체 글, 시스템 정보)
- 5주차(목) - 감정분석 API, e2e 테스트
- 5주차(토) - 유닛 테스트(+ mocking), e2e 테스트(+ 파일 첨부)
- 6주차(화) - ERD
- 2주차(화) - auth, board 모듈 생성 및 테스트 코드 환경 설정
- 2주차(목) - Board, Auth 테스트 코드 작성 및 API 완성
- 3주차(월) - Redis 연결 후 RedisRepository 작성
- 3주차(화) - SignUpUserDto에 ClassValidator 적용
- 3주차(화) - SignIn시 RefreshToken 발급 및 Redis에 저장
- 3주차(화) - 커스텀 AuthGuard 작성
- 3주차(수) - SignOut시 토큰 제거
- 3주차(수) - 깃헙 로그인 구현
- 3주차(토) - OAuth 코드 통합 및 재사용
- 4주차(수) - NestJS + TypeORM으로 MySQL 전문검색 구현
- 4주차(목) - NestJS Interceptor와 로거
- [전체] 10/12(목)
- [전체] 10/15(일)
- [전체] 10/30(월)
- [FE] 11/01(수)~11/03(금)
- [전체] 11/06(월)
- [전체] 11/07(화)
- [전체] 11/09(목)
- [전체] 11/11(토)
- [전체] 11/13(월)
- [BE] 11/14(화)
- [BE] 11/15(수)
- [FE] 11/16(목)
- [FE] 11/19(일)
- [BE] 11/19(일)
- [FE] 11/20(월)
- [BE] 11/20(월)
- [BE] 11/27(월)
- [FE] 12/04(월)
- [BE] 12/04(월)
- [FE] 12/09(금)
- [전체] 12/10(일)
- [FE] 12/11(월)
- [전체] 12/11(월)
- [전체] 12/12(화)