Skip to content

Commit d533dbf

Browse files
authored
Merge pull request #2 from lazyskulptor/master
README.md 작성
2 parents e5ad5cb + bfa6f8e commit d533dbf

File tree

40 files changed

+796
-117
lines changed

40 files changed

+796
-117
lines changed
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
name: Deploy production
2+
on: [push, pull_request]
3+
4+
jobs:
5+
build:
6+
runs-on: ubuntu-latest
7+
container: node:lts-slim
8+
# 아래 defaults 의 run 설정은 node 루트 경로와 저장소 루트가 일치하지 않아 추가됨
9+
# 일반 프로젝트에는 필요없음
10+
defaults:
11+
run:
12+
working-directory: javascript-example
13+
14+
# 서비스 아래의 rdb1, rdb2, cache 값은 다른 값으로 설정 가능하며 해당 서비스의 DOMAIN NAME 으로 사용됩니다.
15+
# 모든 설정 방법은 docker 설정과 일치합니다. 에를 들어 port 값은 좌측은 외부 port 우측은 포워딩 되는 내부 포트입니다.
16+
services:
17+
rdb1:
18+
image: postgres
19+
ports:
20+
- 5432:5432
21+
rdb2:
22+
image: mysql:5
23+
env:
24+
MYSQL_ROOT_PASSWORD: rootpassword
25+
ports:
26+
- 3306:3306
27+
dynamodb:
28+
image: amazon/dynamodb-local
29+
ports:
30+
- 8000:8000
31+
cache:
32+
image: redis:6
33+
ports:
34+
- 6379:6379
35+
36+
steps:
37+
# 아래의 명령문들이 테스트를 하기 위한 설정 및 실행들 입니다. run 하나가 Linux 에서의 명령문입니다.
38+
- uses: actions/checkout@v2
39+
40+
- name: setup node environment
41+
# package-lock.json 파일이 저장소에 있을 때는 npm ci 명령어 사용 가능
42+
run: npm install
43+
44+
- name: Install RDB Dependencies
45+
run: apt-get update && apt-get install -y default-mysql-client
46+
47+
- name: Initialize RDB MySQL
48+
run: mysql -h rdb2 -u root -prootpassword < dump.sql
49+
50+
51+
- name: Initialize RDB Postgres
52+
run: node .github/scripts/initialize_pg.js
53+
54+
- name: Build and Test
55+
run: npx jest
56+
# 상단의 services 에서 설정한 이름의 DOMAIN 값을 사용하기 위해 서비스 내부에 env 값을 설정해줍니다.
57+
# 해당 값을 바라보기 위해선 node, django 서비스 에서 OS 의 env 값을 읽어오는 설정을 해야합니다.
58+
env:
59+
DB_HOST: postgres
60+
NOSQL_HOST: dynamodb
61+
CACHE_HOST: cache
62+
63+
- name: Success
64+
run: echo deployed successfully.
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
name: Deploy production
2+
on: [push, pull_request]
3+
4+
jobs:
5+
build:
6+
runs-on: ubuntu-latest
7+
container: node:lts-slim
8+
# 아래 defaults 의 run 설정은 node 루트 경로와 저장소 루트가 일치하지 않아 추가됨
9+
# 일반 프로젝트에는 필요없음
10+
defaults:
11+
run:
12+
working-directory: typescript-example
13+
14+
# 서비스 아래의 rdb1, rdb2, cache 값은 다른 값으로 설정 가능하며 해당 서비스의 DOMAIN NAME 으로 사용됩니다.
15+
# 모든 설정 방법은 docker 설정과 일치합니다. 에를 들어 port 값은 좌측은 외부 port 우측은 포워딩 되는 내부 포트입니다.
16+
services:
17+
rdb1:
18+
image: postgres
19+
ports:
20+
- 5432:5432
21+
rdb2:
22+
image: mysql:5
23+
env:
24+
MYSQL_ROOT_PASSWORD: rootpassword
25+
ports:
26+
- 3306:3306
27+
dynamodb:
28+
image: amazon/dynamodb-local
29+
ports:
30+
- 8000:8000
31+
cache:
32+
image: redis:6
33+
ports:
34+
- 6379:6379
35+
36+
steps:
37+
# 아래의 명령문들이 테스트를 하기 위한 설정 및 실행들 입니다. run 하나가 Linux 에서의 명령문입니다.
38+
- uses: actions/checkout@v2
39+
40+
- name: setup node environment
41+
# package-lock.json 파일이 저장소에 있을 때는 npm ci 명령어 사용 가능
42+
run: npm install
43+
44+
- name: Install RDB Dependencies
45+
run: apt-get update && apt-get install -y default-mysql-client
46+
47+
- name: Initialize RDB MySQL
48+
run: mysql -h rdb2 -u root -prootpassword < dump.sql
49+
50+
51+
- name: Initialize RDB Postgres
52+
run: node .github/scripts/initialize_pg.js
53+
54+
- name: Build and Test
55+
run: npx jest
56+
# 상단의 services 에서 설정한 이름의 DOMAIN 값을 사용하기 위해 서비스 내부에 env 값을 설정해줍니다.
57+
# 해당 값을 바라보기 위해선 node, django 서비스 에서 OS 의 env 값을 읽어오는 설정을 해야합니다.
58+
env:
59+
DB_HOST: postgres
60+
NOSQL_HOST: dynamodb
61+
CACHE_HOST: cache
62+
63+
- name: Success
64+
run: echo deployed successfully.

README.md

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
# express-jest
2+
3+
## 깨끗한 테스트 코드 유지의 중요성
4+
* **테스트 코드는 실제 코드 못지 않게 중요하다.**
5+
* 테스트 코드에 실제 코드와 동일한 품질 기준을 적용해야 한다.
6+
* 실제 코드가 진화하면 테스트 코드도 변해야 한다.
7+
8+
## 깨끗한 테스트 코드 작성하기
9+
* 깨끗한 테스트 코드를 만들려면? 세 가지가 필요하다. **가독성, 가독성, 가독성.**
10+
* 어쩌면 가독성은 실제 코드보다 테스트 코드에 더더욱 중요하다.
11+
* **이중 표준**
12+
* 실제 코드만큼 효율적일 필요는 없다.
13+
* 가독성을 위해 BUILD-OPERATE-CHECK 패턴이 자주 사용된다.
14+
* [DB 사용을 위한 BOC 패턴을 위한 참조 링크](https://medium.com/swlh/usual-production-patterns-applied-to-integration-tests-50a941f0b04a)
15+
```javascript
16+
//BUILD 테스트를 위한 사전 준비를 한다.
17+
const userToInsert = prepareUser();
18+
19+
//OPERATE 테스트할 코드를 실행한다.
20+
const userFromDB = await service.createUser(userToInsert);
21+
22+
//CHECK 실행 결과를 확인한다.
23+
expect(userFromDB.username).toBe(userToInsert.username);
24+
expect(userFromDB.id).toBe(userToInsert.id);
25+
26+
```
27+
28+
## 테스트의 최종 목적지! TDD 그리고 BDD (테스트주도 개발, 행위주도 개발)
29+
### TDD 법칙 from [테스트 주도 개발], [클린코드]
30+
1. 어떤 코드건 작성하기 전에 실패하는 자동화된 테스트를 작성하라.
31+
1. 실패하는 단위 테스트를 작성할 때까지 실제 코드를 작성하지 않는다.
32+
2. 컴파일은 실패하지 않으면서 실행이 실패하는 정도로만 단위 테스트를 작성한다.
33+
3. 현재 실패하는 테스트를 통과할 정도로만 실제 코드를 작성한다.
34+
2. 중복을 제거하라.
35+
### BDD, TDD 의 연장선으로서
36+
개발자, QA 와 비개발자 모두 한 팀으로 대화를 통해 구체적인 예시와 어떻게 어플리케이션이 작동해야 하는지 만들어가는 방법론입니다.
37+
BDD 는 모든 단위 테스트들이 실사용자의 행동(유스케이스)에 의해 정의되는 것을 말합니다.
38+
##### 예시
39+
> Not BDD
40+
> 1. DB 에 저장되어 있는 사용자 정보를 가져오면 여러 API 에서 사용할거라 가정
41+
> 2. 여러 API 에서 사용할 수 있도록 DB 에서 정보를 가져오는 서비스를 생성
42+
> 3. 서비스를 테스트하는 테스트를 만듬
43+
>
44+
> BDD
45+
> 1. 사용자 로그인 기능이 필요
46+
> 2. 사용자가 할 수 있는 로그인 시도 케이스들을 테스트 코드로 작성
47+
> ... 위에 적힌 TDD 에 맞게 실행함
48+
49+
----
50+
51+
52+
53+
# 샘플코드 안내
54+
55+
샘플코드는 javascript 와 typescript 두 가지로 나뉘어 제공됩니다.
56+
공통되는 부분만 안내합니다. 추가적인 부분은 각 폴더의 README.md 참조 바랍니다.
57+
58+
## 단위 테스트는 [jest](https://jestjs.io/docs/getting-started) 를 사용함
59+
```bash
60+
# 전체 테스트 실행
61+
$ jest
62+
# 원하는 테스트만 실행. <describeString> 는 테스트 그룹, <itString> 는 테스트 이름으로 대체합니다.
63+
$ jest -t '<describeString> <itString>'
64+
```
65+
66+
## endpoint 테스트에 [supertest](https://github.com/visionmedia/supertest#readme) 를 사용함
67+
```javascript
68+
/**
69+
* 반드시 done 함수를 파라미터로 주거나 async 함수를 사용해아 합니다.
70+
* 그렇지 않을 경우 테스트가 프로세스가 멈추지 않습니다.
71+
*/
72+
73+
// callback
74+
it('', (done) => {
75+
request.get('/')
76+
.expect(200)
77+
.end(done);
78+
})
79+
80+
// OR aync
81+
it('', async () => {
82+
await request.get('/')
83+
.expect(200)
84+
.end();
85+
})
86+
```
87+
88+
## CI, CD 환경
89+
실서버와 같은 DB 를 새로 구성해 테스트 합니다.
90+
그러므로 로컬에서도 Mocker 보다 실제 DB 를 사용한 테스트를 권장합니다.
91+
92+
`[GIT_ROOT]/.github/workflows/` 경로에 스크립트가 있어야 합니다.
93+
해당 샘플의 경우 한 저장소에 두 프로젝트가 있지만 일반적인 경우 프로젝트 루트에 .github 폴더가 위치해야 합니다.
94+
javascript, typescript 를 위한 스크립트 동일하게 제작됐습니다.
95+
96+
CI/CD 는 docker 기반으로 사용되기 때문에 사용되는 이미지 별로 환경변수와 명령어들을 위해 docker hub 에서 문서를 참조하는 것이 좋습니다.
97+
해당 스크립트에선 [node](https://hub.docker.com/_/node), [mysql](https://hub.docker.com/_/mysql), [postgres](https://hub.docker.com/_/postgres), [dynamodb](https://hub.docker.com/r/amazon/dynamodb-local/), [redis](https://hub.docker.com/_/redis) 이미지가 사용됐습니다.
98+
99+
----
100+
##### 참고문헌
101+
[클린코드] Robert C. Martin
102+
[테스트 주도 개발] Kent Beck
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
const { Client } = require('pg');
2+
3+
const DB_HOST = process.env.DB_HOST;
4+
const DB_PORT = process.env.DB_PORT || '5432';
5+
6+
//TODO: initialize pgsql.
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
const UserRepo = require('@/repositories/UserRepo').UserRepo;
2+
const data = [];
3+
4+
exports.UserRepoMock = class UserRepoMock extends UserRepo{
5+
constructor() {
6+
super();
7+
}
8+
9+
async insertUser(user) {
10+
const isExist = data.find(rw => {
11+
return user.username === rw.username
12+
});
13+
if(!isExist) {
14+
user.id = String(data.length + 1);
15+
data.push(user);
16+
} else {
17+
throw new Error('Duplicate username');
18+
}
19+
20+
return data.find(
21+
rw => user.username === rw.username
22+
);
23+
}
24+
25+
async findUserByUsername(username) {
26+
return data.find(
27+
rw => username === rw.username
28+
);
29+
}
30+
31+
truncate() {
32+
data.length = 0;
33+
}
34+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
2+
const initUser = function createRandomUser() {
3+
return {
4+
username: randomStr(16),
5+
name: 'asdf',
6+
password: 'asdf'
7+
}
8+
}
9+
10+
const randomStr = function createRandomStr(len) {
11+
const eleList = 'ABCDEFHIJKLMNOPQRSTUVXYZabcdefhijklmnopqrstuvxyz';
12+
let result = '';
13+
Array(len).fill(0).forEach((_, i) => {
14+
const random = Math.floor(Math.random() * 48)
15+
result += eleList[random];
16+
});
17+
return result;
18+
}
19+
20+
module.exports = {
21+
initUser,
22+
randomStr
23+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
const app = require('@/app');
2+
3+
test('null', () => {
4+
const n = null;
5+
console.log(123);
6+
expect(n).toBeNull();
7+
});
8+
9+
//it() 은 test() 의 alias 로 똑같은 함수입니다.
10+
//Test to import file by alias path
11+
it('mount app', () => {
12+
app;
13+
})
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
const app = require("@/app").express;
2+
const supertest = require("supertest");
3+
const userMocker = require("@test/__mocks__/userMocks");
4+
const initUser = userMocker.initUser;
5+
const userService = require("@/services");
6+
7+
const request = supertest(app);
8+
9+
describe('test endpoint', () => {
10+
let usrService = userService.userService;
11+
let userRepo = usrService.repo;
12+
console.log(userRepo);
13+
14+
beforeAll(()=>{
15+
//COMMON BUILD
16+
});
17+
18+
// Callback 방식
19+
it('', (done) => {
20+
request.get('/')
21+
.expect(200)
22+
.end(done);
23+
})
24+
25+
// Async 방식
26+
it('CI 실패, 로그인 시도', async () => {
27+
await request.post('/signin')
28+
.send({username: 'test', password: '1234'})
29+
.set('Accept', 'application/json')
30+
31+
32+
.expect((res) => {
33+
res.body = {result: res.body.result}
34+
})
35+
.expect(200, {
36+
result: false
37+
});
38+
})
39+
40+
it('CI 성공, 로그인 시도', async () => {
41+
const user = initUser();
42+
await userRepo.insertUser(user);
43+
44+
45+
await request.post('/signin')
46+
.send(user)
47+
.set('Accept', 'application/json')
48+
49+
50+
.expect((res) => {
51+
res.body = {result: res.body.result}
52+
})
53+
.expect(200, {
54+
result: true
55+
});
56+
57+
})
58+
59+
afterAll(() => {
60+
userRepo.truncate();
61+
})
62+
})

0 commit comments

Comments
 (0)