# 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) } ``` ![image](https://user-images.githubusercontent.com/48500209/163295338-cacc1cff-1173-4411-a45a-7b5b5de62acf.png) => 어떤 테스트 케이스에서 실패했는지를 알 수가 있음 테스트 코드는 줄이면서 품질은 그대로 가져가는 케이스 <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까지 임의의 정수 맞추기 게임 자바코드라 이해에 중심을 두고 안적음.