# TDD강의, 테스트 주도 개발 기초 Chapter #1, #2
###### tags: `냥하`, `강의`
## Intro
### 과학 vs 엔지니어링
과학: 세상의 많은 현상에 대한 왜?를 밝혀내는 것
엔지니어링: 문제를 정의함
```
=> 엔지니어링에게 과학은 목적이 아닌 수단
```
### 패턴
- 알려진 문제의 일반적이고 재사용할 수 있는 해결법
- 프로그래머는 고유한 문제를 풀어야 한다.
- 고유한 문제는 좀 더 작은 하위 기술 문제(ex, 이미 누군가가 풀어서 풀이법이 있는 문제)를 가진다.
- 이 작은 기술 문제들 중 많은 일부는 과거 어디에선가 여러번 반복되고 해결된 것이 있다.
```
Patterns always have two pars: the how and the when
해법보다 문제에 집중하라. 만병통치약은 없다. 은탄환(silver bullet)은 없다
다양한 무기를 준비해서 잘 꺼내서 쓸 수 있어야한다.
```
## 1부:: 테스트 주도 개발 기초
### 1. 코드 기능 명세
: 프로그래머에게 올바른 명세를 줄 필요가 있다.
<br/><br/>
### 2. 테스트 기법
- 수동테스트
- 소프트웨어 회귀 : 원래는 동작했는데, 어떤 시점 이후로 동작하지 않을 때
- 대상: 시간이 갈수록 회귀 테스트의 대상이 늘어남, 코드는 변경된 영역 외 기존 영역에도 영향을 미칠 수 있기 때문.
- 테스트 자동화
- 기능을 검증하는 코드 작성
- 테스트 코드 작성 비용이 소비되지만, 실행 비용이 낮고 결과의 신뢰도가 높음
- 테스트 코드 작성과 관리가 프로그래머 역량의 영향을 크게 받음.
- 인수 테스트
- 만들어진 소프트웨어를 사용자에게 인수하면서 검증하는 과정, 사용자의 환경과 가장 비슷한 환경(시스템 전체 내용 Deploy후)에서 진행
- 배치가 완료된 시스템을 대상으로 검증
- 신뢰도가 높음
- 높은 비용(작성비용, 관리비용, 실행비용)
- 피드백 품질이 낮음
- 단위 테스트
- 시스템 일부(하위 시스템)을 대상으로 검증
- 낮은 비용
- 높은 피드백 품질
- 전체 시스템 이상 여부 신뢰도가 낮음
<br/><br/>
### 3. 코드 분해
:문제를 나눌 수 있다. 큰 시스템은 더 작은 하위 시스템으로 분해 가능, 교체 가능
- 반복되는 문제의 풀이는 재사용이 가능하다
- 코드는 여러번 읽히기 때문에, 코드 가독성이 중요하다
- 소프트웨어 개발비용 절감 ( 엔지니어링 철학 관점 )
#### 모듈화
- 모듈 재사용,라이브러리
- 인터페이스(모듈이 어떤 기능을 나타내는 지에 대한 설계) - 구현
<br/><br/>
### 4. 단위 테스트
`npm install jest`
package.json -> npm test를 `jest --watch` 로 변경
```
// 공백 압축
function refineText(s) {
return s.replace(" ", " ").replace(" ", " ")
}
```
```
// index.test.js
const sut = require("./index") // 테스트 대상 시스템 : system under test
test('sut transform "hello world" to "hello world"'),() => {
// 사이 공백 2개 > 1개가 되는지?
const actual = sut("hello. world");
expect(actual).toBe("hello world");
});
```
이러한 테스트 코드를 여러개 짜고싶다고 생각했을때,
여러번 쓰는 것이 아닌 반복문 처리
```
test('sut correctly works', () => {
for(const source of ['hello world', 'hello world', 'hello world']) {
// 공백이 각각 2개, 3개, 4개인 것에 대한 테스트
const actual = sut(source);
expect(actual).toBe("hello world")
}
})
```
위 테스트 코드의 문제점
- 어떤 테스트 케이스에서 실패 했을지를 알 수 없음
- 중간에 실패하면 뒤에까지 실행이 안됨 -> 코드는 줄어들었지만, 피드백 품질이 낮아짐
해결 방법
- parameter test `describe.each`: 동일 코드에서 인자만 달리해서 테스트를 할 수 있는 기법
```
test.each`
source | expected
${"hello world"} | ${"hello world"}
${"hello world"} | ${"hello world"}
${"hello world"} | ${"hello world"}
`('sut transforms "$source" to "$expected"', {(source, expected)}) => {
const actual = sut(source);
expect(actual).toBe(expected)
}
```

=> 어떤 테스트 케이스에서 실패했는지를 알 수가 있음
테스트 코드는 줄이면서 품질은 그대로 가져가는 케이스
<br/><br/>
### 5. 테스트 우선 개발
: 운영 코드보다 테스트 코드를 먼저 작성, 코드 기능명세와 매우 유사
가시적이고 구체적인 목표 | 자가 검증 | 반복 실행 | 클라이언트
- 운영 코드보다 테스트 코드를 먼저 작성
- 명확하고 검증 가능한 목표를 설정한 후 목표를 달성
- 프로세스가 코딩에 앞선 목표 설정을 강요
- 프로그래머는 자신이 풀어야 할 문제를 구체적으로 이해해야함
강의 플로우: 계속 test code를 작성하고, 이를 통과하는 운영코드를 작성하는 방법
😿 느낀점 => 뭔가 주니어단계에서 tdd를 하다보면 볼품없는 하드코딩하는 운영코드를 작성하게될까바 더 안하게 됐던듯
`npm i faker` => 임의의 문자열을 넣어서 테스트
```javascript=
describe("given banned word", () => {
const bannedWord = faker.lorem.word()
const source = "hello " + bannedWord;
const expeceted = "hello " + "*".repeat(bannedWord.length);
test(`${bannedword} when invoke sut then it returns ${expected}`, () => {
const actual = sut(source, {bannedWords: [bannedWord]});
expect(actual).toBe(expected);
})
})
```
운영코드
```javascript=
function refineText(s, options) { // 금지어를 사용하는 case에서만 options 파라미터를 넣고싶음
s.replace(" ", " ").replace(" ", " ")
if(options) {
for(const bannedWord in option.bannedWords) {
s = s.replace(bannedWord, "*".repeat(bannedWord.length));
}
}
return s;
}
```
<br/><br/>
### 6. 정리된 코드(리팩토링)
- 작업환경 정리
- 리팩터링
- cf, 팩터링: 숫자나 식을 여러 요소로 나누는 것, 요소의 크기는 단순하게
- 의미를 유지하며 코드 베이스를 정리하는 것
- 전체 시스템의 의미를 유지하며, 하위 시스템의 내부 구조를 개선하는 것을 의미함
- 의미 유지를 확인하는 방법: 테스트
- 강의 실습: 메서드를 추출 / if문을 switch문으로 변경(자바 화살표 모야...?) / 쓰지않는 parameter제거 / 가독성 up 등등.
- => 이렇게 코드를 바꾸면서 중간중간 테스트코드를 계속 실행시켜봄(의미 유지)
```javascript=
// 테스트는 바꾸지 않고, 운영코드를 개선하는 것이 원칙
function refineText(source, options) {
// 금지어를 사용하는 case에서만 options 파라미터를 넣고싶음
[normalizeWhiteSpaces, compactWhiteSpaces, maskBannedWords].reduce((value, filter) => filter(value, options), source)
// source = normalizeWhiteSpaces(source);
// source = compactWhiteSpaces(source);
// source = maskBannedWords(source, options);
return source;
}
function normalizeWhiteSpaces(source) {
return source.replace("\t", " ");
}
// 연쇄적으로 replace하던 긴 코드를 추출함
function compactWhiteSpaces(source) {
return source.indexOf(" ") < 0 ? source : compactWhiteSpaces(source.replace(" ", " "));
}
function maskBannedWords(source, options) {
if(options) {
for(const bannedWord in option.bannedWords) {
source = source.replace(bannedWord, "*".repeat(bannedWord.length));
}
}
return source;
}
```
<br/><br/>
### 7. 테스트 주도 개발
- 절차: RED -> GREEN -> REFACTOR
- RED: 테스트의 실패를 확인하는 단계
- GREEN: 운영 코드를 수정해서 테스트를 모두 성공시키는 과정
- REFACTOR: 테스트 통과를 유지하며 리팩터링 진행
- 테스트 실패: 테스트코드가 실패하는지 확인하는것이 중요
- 테스트 성공: 테스트 성공은 요구사항 만족을 의미
- 켄트 백의 설계 규칙
1순위: 테스트 통과
2순위: 코드의 의도드러내기, 가독성, 중복 코드 제거(but 의도와 상충할 수 있음)
```javascript=
trimWhitespaces
function trimWhitespaces(value) {
return value.trim()
}
```
<br/><br/>
### 8. 프로그래머 피드백
- 기대 출력 피드백
1. 사용자 피드백: 사용자가 직접코드를 사용한 후 경험한 버그나 불만을 제보
2. Quality Assurance(QA): 전문 인적 자원에 의한 인수 테스트
3. 프로그래머 테스트: 프로그래머가 직접 피드백 장치를 준비
- 코드 작성할때는 비용 up, 실행 비용은 down
- 문제 조기발견 가능
4. 도구 피드백: 컴파일 오류, 정적 검사 등 프로그래머가 사용하는 도구가 제공하는 피드백
- 오버엔지니어링
- 프로그래머는 요구사항 명세에 명확히 지정되지 않은 성능 달성이나 구현 설계 품질 개선에 빠져드는 경향을 가짐
- 모든 테스트에서 GREEN을 확인하면 넘어갈줄도 알아야함
=> 핵심은 피드백
- 테스트 주도 개발의 핵심은 정해진 절차가 아니라 짧은 주기로 지속되는 피드백
- 피드백에 기반해 안정적으로 지식과 코드를 늘려나가는 것이 목적.
### 9. 실습(장난감)
1부터 100까지 임의의 정수 맞추기 게임
자바코드라 이해에 중심을 두고 안적음.