Skip to content

[재하] 1114(화) 개발기록

박재하 edited this page Nov 14, 2023 · 1 revision

목표

  • 12시부터 페어 프로그래밍
    • 테스트 코드 작성
    • board, auth CRUD 구현
  • 개발 기록 정리
    • ncp 서버 생성 과정
    • docker 이미지 생성 과정
    • TDD 과정
  • 9시에 멘토님과 멘토링

목차

  • Repository 동기화 및 작업 브랜치 설정
  • CRUD Generator를 이용한 board, auth 모듈 생성
  • 실패하는 Unit Test Code 작성

Repository 동기화 및 작업 브랜치 설정

이번에 팀 레포 프로젝트 폴더를 완전히 갈아 엎으면서 fork로 떠온 개인 레포에서 다시 upstream(팀 레포)를 fetch, rebase할 일이 생겼다.

사실 fork해온 저장소를 지우고 다시 fork를 했으면 쉽게 세팅이 됐겠지만 git 연습도 해볼 겸 그대로 fetch, rebase를 진행하였다.

  1. fork repo git clone

https://user-images.githubusercontent.com/138586629/282657313-8f8e50e5-f268-423b-ad39-bc1e56b8e9d0.png

  1. remote add upstream

https://user-images.githubusercontent.com/138586629/282657322-fe700b1f-a154-499b-a95e-7242d052e405.png

  1. fetch, reset upstream main

https://user-images.githubusercontent.com/138586629/282657325-666438c6-21f5-4969-9c7e-beccd83ae753.png

  1. git log

https://user-images.githubusercontent.com/138586629/282657331-a3ae2f98-5658-4831-b6eb-617f7eded392.png

  1. git push -f origin main ( fork해온 개인 repo에 push)

https://user-images.githubusercontent.com/138586629/282657335-74d05e0a-49f2-4d18-b50a-be887dd14323.png

  1. fetch, rebase be-develop (upstream의 be-develop 브랜치 fetch, rebase)

https://user-images.githubusercontent.com/138586629/282657337-7bf89c75-2768-4cec-9c24-bd121e800e4c.png

  1. git log 확인

https://user-images.githubusercontent.com/138586629/282657331-a3ae2f98-5658-4831-b6eb-617f7eded392.png

  1. rebase be-develop with upstream main

https://user-images.githubusercontent.com/138586629/282657342-b1cbe5f4-a06e-46b8-b71c-12b4f20d490d.png

  1. push origin be-develop

https://user-images.githubusercontent.com/138586629/282658989-6a601a22-4e71-4dc2-92e4-8e825519727c.png

  1. final git graph

https://user-images.githubusercontent.com/138586629/282657344-610a8b82-e3d4-4cfc-9588-e65ca5a22663.png

CRUD Generator를 이용한 board, auth 모듈 생성

프로젝트 설정 (yarn berry)

현재 프로젝트 be-develop 브랜치에 대한 local PC로의 pull을 완료한 후 yarn install을 해준다.

yarn install
스크린샷 2023-11-14 오후 1 35 33

VSCode의 경우는 아래와 같이 @yarnpkg/sdks, vscode를 dlx로 세팅해줘야 함.

yarn dls @yarnpkg/sdks vscode
스크린샷 2023-11-14 오후 1 15 01

TypeScript 버전을 Workspace 버전으로 할 것인지 묻는 알럿창이 뜨면 허용하고, 그러면 관련된 vscode 설정이 프로젝트 루트에 추가됨.

스크린샷 2023-11-14 오후 2 00 29
// settings.json
{
	"search.exclude": {
		"**/.yarn": true,
		"**/.pnp.*": true
	},
	"prettier.prettierPath": ".yarn/sdks/prettier/index.cjs",
	"typescript.tsdk": ".yarn/sdks/typescript/lib",
	"typescript.enablePromptUseWorkspaceTsdk": true
}

gitignore해주자.

nest g resource를 활용해 user, board 모듈/컨트롤러/서비스 및 CRUD 구현

nest g resource

nest g module, controller, service가 아닌 nest g resource를 활용해 한번에 생성해주자. 이 기능을 활용하면 CRUD Generator로 CRUD 메소드까지 같이 생성해준다. (학습메모 2)

yarn workspace server nest g resource

프로젝트 루트 폴더는 위 명령을 통해 만들어줄 수 있다.

스크린샷 2023-11-14 오후 1 08 55

같은 방식으로 auth, board 모두 생성해줬다.

jest test파일

자동 생성된 모든 test 파일을 test 폴더에 계층(모듈)별로 폴더를 만들어 옮긴 후, jest 설정을 바꾸어 이를 인식할 수 있게 변경해주자.

// package.json
{
  ...
	"jest": {
		...
		"rootDir": "test",
		"testRegex": ".*\\.(e2e-)?spec\\.ts$",
		...
	}
}

rootDir을 src에서 test로, testRegex는 e2e-spec.ts도 잡아줄 수 있도록 .*\\.spec\\.ts$에서 .*\\.(e2e-)?spec\\.ts$로 변경해준다.

// package.json
{
	...
	"jest": {
		...
		"verbose": true,
		"collectCoverage": true,
		...
	}
}

추가로 상세 describe와 coverage까지 출력할 수 있도록 verbosecollectCoveragetrue로 설정해주는 구문을 package.json에 추가해준다.

확인해보자.

yarn test

루트 디렉토리에서 위에 해당하는 명령은

yarn workspace server test
스크린샷 2023-11-14 오후 1 55 14

원래 이랬던 게

스크린샷 2023-11-14 오후 2 39 30

아? rootDir이 test라서 src 내의 파일들을 못잡아줌..

추가적인 설정이 필요하겠다.

고군분투 끝에 collectCoverageFrom 설정에서 src만 잡아주도록 수정해줄 수 있었다.

{
	...
	"jest": {
		...
		"rootDir": ".",
		...
		"collectCoverageFrom": [
			"<rootDir>/src/**/*.(t|j)s"
		],
		"coverageDirectory": "./coverage",
		...
	}
}
스크린샷 2023-11-14 오후 2 37 25

이렇게 이쁘게 나온다! coverage는 앞으로 열심히 올려보자.

최종 package.json은 다음과 같다.

// package.json
{
	...
	"jest": {
		"moduleFileExtensions": [
			"js",
			"json",
			"ts"
		],
		"rootDir": ".",
		"testRegex": ".*\\.(e2e-)?spec\\.ts$",
		"transform": {
			"^.+\\.(t|j)s$": "ts-jest"
		},
		"verbose": true,
		"collectCoverage": true,
		"collectCoverageFrom": [
			"<rootDir>/src/**/*.(t|j)s"
		],
		"coverageDirectory": "./coverage",
		"testEnvironment": "node"
	}
}

실패하는 Unit Test Code 작성

학습메모 3dm

Auth Controller

// auth.controller.spec.ts
import { Test, TestingModule } from '@nestjs/testing';
import { AuthController } from '../../src/auth/auth.controller';
import { AuthService } from '../../src/auth/auth.service';

describe('AuthController', () => {
	let controller: AuthController;

	beforeEach(async () => {
		const module: TestingModule = await Test.createTestingModule({
			controllers: [AuthController],
			providers: [AuthService],
		}).compile();

		controller = module.get<AuthController>(AuthController);
	});

	it('should be defined', () => {
		expect(controller).toBeDefined();
	});
});

기본 생성되는 코드는 위와 같다. CRUD 메소드에 대한 테스트 코드를 copilot의 도움을 받아 추가해봤다.

// auth.controller.spec.ts
import { Test, TestingModule } from '@nestjs/testing';
import { AuthController } from '../../src/auth/auth.controller';
import { AuthService } from '../../src/auth/auth.service';

describe('AuthController', () => {
	let controller: AuthController;
	let service: AuthService;

	beforeEach(async () => {
		const module: TestingModule = await Test.createTestingModule({
			controllers: [AuthController],
			providers: [AuthService],
		}).compile();

		controller = module.get<AuthController>(AuthController);
		service = module.get<AuthService>(AuthService);
	});

	it('should be defined', () => {
		expect(controller).toBeDefined();
	});

	describe('findAll', () => {
		it('should be defined', () => {
			expect(controller.findAll).toBeDefined();
		});

		it('should return an array of auth', async () => {
			const result = ['test'];
			jest.spyOn(service, 'findAll').mockImplementation((): any => result);

			expect(await controller.findAll()).toBe(result);
		});
	});

	describe('findOne', () => {
		it('should be defined', () => {
			expect(controller.findOne).toBeDefined();
		});

		it('should return an auth', async () => {
			const result = 'test';
			jest.spyOn(service, 'findOne').mockImplementation((): any => result);

			expect(await controller.findOne('1')).toBe(result);
		});
	});

	describe('create', () => {
		it('should be defined', () => {
			expect(controller.create).toBeDefined();
		});

		it('should return an auth', async () => {
			const result = 'test';

			jest.spyOn(service, 'create').mockImplementationOnce((): any => result);

			expect(await controller.create({})).toMatchObject(result);
		});
	});

	describe('update', () => {
		it('should be defined', () => {
			expect(controller.update).toBeDefined();
		});

		it('should return an auth', async () => {
			const result = 'test';
			jest.spyOn(service, 'update').mockImplementation((): any => result);

			expect(await controller.update('1', {})).toBe(result);
		});
	});

	describe('remove', () => {
		it('should be defined', () => {
			expect(controller.remove).toBeDefined();
		});
	});
});

여기서 mocking되지 않은 service를 또 테스트해봐야 되지 않을까 하는 의문이 생겨, 열띤 토론끝에 오늘 백엔드 멘토님과의 멘토링 시간에 질문을 드려보기로 했다. TDD는 대체 어떤 방식으로 하는 걸까? 지금 Entity 속성이 없는데 이건 어떤 순서로 개발해야 하는거지?

Entity 테스트 코드 작성 -> Entity 구현 -> Controller 테스트 코드 작성 -> Controller 구현 -> Service 테스트 코드 작성 -> Service 구현 -> Repository 테스트 코드 작성

이런 순서가 맞는건지, Repository는 이미 정의된 메소드를 쓰는데 또 이게 실제로 들어가는지 확인하려면 어떤 방식으로 테스트를 해야 하는지가 막막했다.

// 예를 들면 이런식으로 해야하는게 아닐까?

describe('create', () => {
	it('should be defined', () => {
		expect(controller.create).toBeDefined();
	});

	it('should return an auth (mock service)', async () => {
		const result = 'test';

		jest.spyOn(service, 'create').mockImplementationOnce((): any => result);

		expect(await controller.create({})).toMatchObject(result);
	});

	it('should return an auth (real)', async () => {
		// given
		const user = 'test'; // TODO : 제대로된 User 인스턴스가 들어가야 함.

		// when
		const result: any = await controller.create(user);

		// then
		expect(result).toBeInstanceOf(User);
		expect(result).toMatchObject(user);
	});

	it('should assign new id (real)', async () => {
		// given
		const user = 'test'; // TODO : 제대로된 User 인스턴스가 들어가야 함.

		// when
		const result: any = await controller.create(user);

		expect(result).toHaveProperty('id');
		expect(result.id).toBeInstanceOf(Number);
	});
});

그래서 여기까지 줄이고 TDD와 동시성 제어와 관련해 질문지를 꼼꼼히 작성하고 멘토링 후 TDD를 계속 진행해보기로 했다!

참고삼아 Repository를 mocking한 Service 유닛테스트 코드 예시까지만 남기고 오늘의 개발기록을 마친다.

// board.service.spec.ts
import { Test, TestingModule } from '@nestjs/testing';
import { BoardService } from './board.service';
import { Board } from './entities/board.entity';

describe('BoardService', () => {
	let service: BoardService;
	let repository: Repository<Board>;

	beforeEach(async () => {
		const module: TestingModule = await Test.createTestingModule({
			providers: [
				BoardService,
				{
					provide: getRepositoryToken(Board),
					useClass: Repository,
				},
			],
		}).compile();

		service = module.get<BoardService>(BoardService);
		repository = module.get<Repository<Board>>(getRepositoryToken(Board));
	});

	it('should be defined', () => {
		expect(service).toBeDefined();
	});

	describe('findAll', () => {
		it('should be defined', () => {
			expect(service.findAll).toBeDefined();
		});
		it('should return an array of boards', async () => {
			const board = new Board();
			board.id = 1;
			board.title = 'test';
			board.content = 'test';
			board.image = null;
			board.star_style = 'test';
			board.star_position = 'POINT(0, 0, 0)';
			board.author = 'test';
			board.created_at = new Date();
			board.modified_at = new Date();
			const boards = [board];

			jest.spyOn(repository, 'find').mockImplementation(async () => boards);

			expect(await service.findAll()).toBe(boards);
		});
	});
});

학습메모

  1. [GIT] Conflict(충돌) 났을 때 강제로 Pull 하기.
  2. NestJS CRUD Generator
  3. NestJS, TDD로 개발하기
  4. NestJS Mocking하기
  5. Repository 테스트하기

소개

규칙

학습 기록

[공통] 개발 기록

[재하] 개발 기록

[준섭] 개발 기록

회의록

스크럼 기록

팀 회고

개인 회고

멘토링 일지

Clone this wiki locally