9장 단위 테스트
생성일
2024년 2월 27일 오후 3:09
태그
비어 있음
단위 테스트를 자동화하는 프로그래머들이 점점 증가하고 있지만 그 와중에 많은 프로그래머들이 제대로 된 테스트 케이스를 작성해야 한다는 좀 더 중요한 사실을 놓쳐버렸다.
🧪 TDD 법칙 세 가지
첫째 법칙 : 실패하는 단위 테스트를 작성할 때까지 실제 코드를 작성하지 않는다.
둘째 법칙 : 컴파일은 실패하지 않으면서 실행이 실패하는 정도로만 단위 테스트를 작성한다.
셋째 법칙: 현재 실패하는 테스트를 통과할 정도로만 실제 코드를 작성한다.
이렇게 하면 실제코드가 테스트 코드가 완성된 30초 후에 나오게 된다.
그리고 실제 코드와 맞먹을 정도로 방대한 테스트 코드는 심각한 관리 문제를 유발하기도 한다.
🧪 깨끗한 테스트 코드 유지하기
지저분한 테스트 코드를 내놓는 것은 테스트를 안 하는 것보다 오히려 더 못하다.
실제 코드가 변하면 테스트 코드도 변경해야하지만 테스트 코드가 복잡하면 변경하기 어려워진다.
새 버전이 출시될 때 마다 팀이 테스트 케이스를 유지하고 보수하는 비용도 늘어난다.
결국 테스트 슈트를 폐기하지 않으면 안 되는 상황에 처한다.
하지만 테스트 슈트가 없으면 수정을 했을 때 시스템의 모든 부분이 안전하다는 사실을 검증할 수 없다.
저자가 참여한 많은 팀이 깨끗한 단위 테스트로 성공했다.
테스트 코드는 실제 코드 못지 않게 중요하다.
코드에 유연성, 유지보수성, 재사용성을 제공하는 버팀목은 단위 테스트이다.
테스트 케이스가 있으면 변경이 두렵지 않다. 아키텍처와 설계도 개선할 수 있다.
🧪 깨끗한 테스트 코드
깨끗한 테스트 코드를 만드려면 가독성이 중요하다
목록 9-1
addPage, assertSubString이 많이 중복된다.
Java
복사
// 목록 9-1 SerializedPageResponderTest.java
public void testGetPageHieratchyAsXml() throws Exception {
crawler.addPage(root, PathParser.parse("PageOne"));
crawler.addPage(root, PathParser.parse("PageOne.ChildOne"));
crawler.addPage(root, PathParser.parse("PageTwo"));
request.setResource("root");
request.addInput("type", "pages");
Responder responder = new SerializedPageResponder();
SimpleResponse response =
(SimpleResponse) responder.makeResponse(new FitNesseContext(root), request);
String xml = response.getContent();
assertEquals("text/xml", response.getContentType());
assertSubString("<name>PageOne</name>", xml);
assertSubString("<name>PageTwo</name>", xml);
assertSubString("<name>ChildOne</name>", xml);
}
public void testGetPageHieratchyAsXmlDoesntContainSymbolicLinks() throws Exception {
WikiPage pageOne = crawler.addPage(root, PathParser.parse("PageOne"));
crawler.addPage(root, PathParser.parse("PageOne.ChildOne"));
crawler.addPage(root, PathParser.parse("PageTwo"));
PageData data = pageOne.getData();
WikiPageProperties properties = data.getProperties();
WikiPageProperty symLinks = properties.set(SymbolicPage.PROPERTY_NAME);
symLinks.set("SymPage", "PageTwo");
pageOne.commit(data);
request.setResource("root");
request.addInput("type", "pages");
Responder responder = new SerializedPageResponder();
SimpleResponse response =
(SimpleResponse) responder.makeResponse(new FitNesseContext(root), request);
String xml = response.getContent();
assertEquals("text/xml", response.getContentType());
assertSubString("<name>PageOne</name>", xml);
assertSubString("<name>PageTwo</name>", xml);
assertSubString("<name>ChildOne</name>", xml);
assertNotSubString("SymPage", xml);
}
public void testGetDataAsHtml() throws Exception {
crawler.addPage(root, PathParser.parse("TestPageOne"), "test page");
request.setResource("TestPageOne"); request.addInput("type", "data");
Responder responder = new SerializedPageResponder();
SimpleResponse response =
(SimpleResponse) responder.makeResponse(new FitNesseContext(root), request);
String xml = response.getContent();
assertEquals("text/xml", response.getContentType());
assertSubString("test page", xml);
assertSubString("<Test", xml);
}
목록 9-2
build-operate-check 패턴으로
테스트 자료 만듬 - 테스트 자료 조작 - 조작 결과가 올바른지 확인
잡다하고 세세한 코드를 없앰
DSL로 테스트 코드 구현
Java
복사
// 목록 9-2 SerializedPageResponderTest.java (리팩토링한 코드)
public void testGetPageHierarchyAsXml() throws Exception {
makePages("PageOne", "PageOne.ChildOne", "PageTwo");
submitRequest("root", "type:pages");
assertResponseIsXML();
assertResponseContains(
"<name>PageOne</name>", "<name>PageTwo</name>", "<name>ChildOne</name>");
}
public void testSymbolicLinksAreNotInXmlPageHierarchy() throws Exception {
WikiPage page = makePage("PageOne");
makePages("PageOne.ChildOne", "PageTwo");
addLinkTo(page, "PageTwo", "SymPage");
submitRequest("root", "type:pages");
assertResponseIsXML();
assertResponseContains(
"<name>PageOne</name>", "<name>PageTwo</name>", "<name>ChildOne</name>");
assertResponseDoesNotContain("SymPage");
}
public void testGetDataAsXml() throws Exception {
makePageWithContent("TestPageOne", "test page");
submitRequest("TestPageOne", "type:data");
assertResponseIsXML();
assertResponseContains("test page", "<Test");
}
목록 9-3, 9-4, 9-5
9-3 heaterState상태를 보고 assertTrue를 읽게 된다. 테스트 코드를 읽기 어렵다.
9-4,9-5 가독성을 높혔다.
wayTooCold라는 함수를 만들어 tic 함수를 숨겼다.
문자열은 {heater, blower, cooler, hi-temp-alarm-lo-temp-alarm}
대문자는 켜짐, 소문자는 꺼짐을 뜩한다.
”그릇된 정보를 피하라” 규칙을 위반하지만 여기서는 적절한 경우이다.
문자열을 따라서 결과를 빠르게 판단할 수 있다.
Java
복사
// 목록 9-3 EnvironmentControllerTest.java
@Test
public void turnOnLoTempAlarmAtThreashold() throws Exception {
hw.setTemp(WAY_TOO_COLD);
controller.tic();
assertTrue(hw.heaterState());
assertTrue(hw.blowerState());
assertFalse(hw.coolerState());
assertFalse(hw.hiTempAlarm());
assertTrue(hw.loTempAlarm());
}
Java
복사
// 목록 9-4 EnvironmentControllerTest.java(리팩터링)
@Test
public void turnOnLoTempAlarmAtThreshold() throws Exception {
wayTooCold();
assertEquals("HBchL", hw.getState());
}
Java
복사
// 목록 9-5 EnvironmentControllerTest.java (더 복잡한 선택)
@Test
public void turnOnCoolerAndBlowerIfTooHot() throws Exception {
tooHot();
assertEquals("hBChl", hw.getState());
}
@Test
public void turnOnHeaterAndBlowerIfTooCold() throws Exception {
tooCold();
assertEquals("HBchl", hw.getState());
}
@Test
public void turnOnHiTempAlarmAtThreshold() throws Exception {
wayTooHot();
assertEquals("hBCHl", hw.getState());
}
@Test
public void turnOnLoTempAlarmAtThreshold() throws Exception {
wayTooCold();
assertEquals("HBchL", hw.getState());
}
9-6 테스트 api와 실제코드에 적용하는 표준이 다른 경우
예시 코드는 임베디드 시스템이므로 컴퓨터 자원과 메모리가 제한적이지만 테스트 환경에서는 그렇지 않다.
보기에 흉한 StringBuffer보다는 String을 사용한다.
*StringBuffer는 기존의 객체로 추가 변경돼 문자열을 바로 추가할 수 있으므로, 공간의 낭비도 없으며 속도도 매우 빠르다.
→ 이중표준의 본질.
Java
복사
// 목록 9-6 MockControlHardware.java
public String getState() {
String state = "";
state += heater ? "H" : "h";
state += blower ? "B" : "b";
state += cooler ? "C" : "c";
state += hiTempAlarm ? "H" : "h";
state += loTempAlarm ? "L" : "l";
return state;
}
테스트 당 개념 하나
중복이 생길 수는 있지만 단일 assert문을 사용하는 것이 좋다고 생각한다.
TEMPLATE METHOD, 독자적인 클래스에 given, then 분리하는 방법이 있다.
여러 개념을 한 테스트에 넣으면 독자는 각 절이 존재하는 이유를 모두 알아야 하고, 각 절이 테스트하는 개념을 모두 이해 해야 한다.
⇒ 테스트 함수 하나는 개념 하나만 테스트하라
first
Fast : 테스트는 빨라야한다..
Independent : 테스트는 서로 의존하면 안된다.
Repeatable : 테스트는 어떤 환경에서도 반복 가능해야 한다.
Self-Validating : 테스트는 bool 값으로 결과를 내야 한다.
Timely : 테스트는 적시에 작성 해야 한다.
조혜온
2024. 02. 27.
중복
조혜온
2024. 02. 27.
중복
조혜온
2024. 02. 27.
웹 로봇이 사용하는 객체
테스트와 무관해서 테스트 코드의 의도를 흐림
조혜온
2024. 02. 27.
리스트 보기
'책 > Clean Code' 카테고리의 다른 글
[Clean Code] 13장 동시성 (0) | 2025.03.17 |
---|---|
[Clean Code] 11장 시스템 (0) | 2025.03.17 |
[Clean Code] 7 장 오류 처리 (0) | 2025.03.17 |
[Clean Code] 5장 형식 맞추기 (0) | 2025.03.17 |
[Clean Code] 3장 함수 (0) | 2024.01.30 |