Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 |
Tags
- 2206번
- 백준
- 9장
- java
- java의 정석
- 11758번
- 자바의 정석
- 코딩테스트
- Design Pattern
- springboot
- 2166번
- 프로그래머스
- 17장
- 가장 긴 증가하는 부분 수열2
- SerialDate 리펙터링
- programmers
- 10830번
- DxTrace
- 11286번
- Dxerr.h
- 2156번
- 냄새와 휴리스틱
- 클린코드
- 1300번
- 1043번
- 코딩 테스트
- Spring
- BOJ
- Adapater Pattern
- Design Patterns
Archives
- Today
- Total
Don't give up!
[클린코드] 9장 단위테스트 본문
이 글은 로버트 마틴의 '클린 코드'를 읽고 TIL(Today I Learned)로써 정리한 내용을 기록하는 글입니다.
자세한 내용은 책을 통해 확인하실 수 있습니다.
알라딘: 클린 코드 Clean Code (aladin.co.kr)
단위 테스트
- 단위 테스트는 모듈(클래스)이 정상적으로 작동하는지 모든 메소드에 대해 테스트하는 것이다.
- 테스트를 추가하려고 급하게 서두르는 와중에 많은 프로그래머들이 제대로 된 테스트 케이스를 작성해야한다는 중요한 사실을 놓쳐버린다.
TDD(Test Driven Development)
- TDD는 단위 테스트를 작성하고 이를 통과하는 실제 코드를 작성하는 단계를 반복하는 개발 프로세스이다.
- TDD는 다음의 법칙을 따른다.
- 실패하는 단위 테스트를 작성할 때까지 실제 코드를 작성하지 않는다.
- 컴파일은 실패하지 않으면서 실행이 실패하는 정도로만 단위 테스트를 작성한다.
- 현재 실패하는 테스트를 통과할 정도로만 실제 코드를 작성한다.
- 실제 코드와 맞먹을 정도로 방대한 테스트 코드는 심각한 관리 문제를 유발하기도 한다.
깨끗한 테스트 코드
- 지저분한 테스트 코드는 테스트를 안 한것보다 못하다.
- 실제 코드가 변화하면 테스트 코드도 변화해야 한다.
- 테스트 코드가 복잡할수록 실제 코드를 짜는 시간보다 테스트 케이스를 추가하는 시간이 더 걸린다.
- 새 버전을 출시할 때마다 팀이 테스트 케이스를 유지하고 보수하는 비용도 늘어난다.
- 그럼에도 테스트 슈트가 없으면 개발자는 자신이 수정하느 코드가 제대로 도는지 확인할 방법이 없다.
- 테스트 코드는 실제 코드 못지 않게 중요하다.
테스트는 유연성, 유지보수성, 재사용성을 제공한다.
- 테스트 코드를 깨끗하게 유지하지 않으면 결국은 잃어버린다.
- 테스트 케이스가 없으면 실제 코드를 유연하게 만드는 버팀목도 사라진다.
- 단위 테스트는 코드에 유연성, 유지보수성, 재사용성을 제공하는 버팀목이다.
- 테스트 커버리지가 높을 수록 버그가 생길 수 있다는 공포가 줄어든다.
- 테스트 케이스가 있으면 변경이 쉬워진다.
- 반대로 테스트 코드가 지저분하면 코드를 변경하는 능력이 떨어지며 코드 구조를 개선하는 능력도 떨어진다. (실제 코드도 지저분해진다.)
깨끗한 테스트 코드를 만들기 위한 조건
- 가독성을 실제 코드보다 테스트 코드에 더더욱 중요하다.
- 테스트 코드는 최소의 표현으로 많은 것을 나타내야 한다.
//request에 대한 response를 테스트하는 테스트.
//나쁜 예시 - 테스트 내용에 crawler, PathParser의 함수가 여러번 호출되어 테스트 의도를 헷갈리게한다.
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);
}
//BUILD-OPERATE-CHECK 패턴으로 진짜 필요한 자료 유형과 함수만 사용 (의미 있는 함수 이름으로 내용을 쉽게 짐작할 수 있음)
public void testGetPageHierarchyAsXml() throws Exception {
makePages("PageOne", "PageOne.ChildOne", "PageTwo");
submitRequest("root", "types:pages");
assertResponseIsXml();
assertResponseContains("<name>PageOne</name>", "<name>PageTwo</name>", "<name>ChildOne</name>");
}
도메인에 특화된 언어(Domain Specific Language)
- 도메인 특화 언어(DSL)는 특정한 분야에 최적화된 프로그래밍 언어이다.
- 도메인에 따라 조율된 언어를 사용하기 때문에 중요하지 않은 복잡한 요소에서 필수 요소만 분리할 수 있다.
- 흔히 쓰는 시스템 조작 API를 사용하는 대신 API 위에 함수와 유틸리티를 구현한 후 이를 사용하므로 테스트 코드를 짜기도 읽기도 쉬워진다.
- 이렇게 구현한 함수와 유틸리티는 테스트 코드에서 사용하는 특수 API가 된다. (테스트를 구현하는 당사자와 테스트를 읽을 독자를 도와주는 테스트 언어이다.)
- 테스트 API는 잡다하고 세세한 사항으로 범벅된 코드를 계속 리펙터링하다가 진화되는 API이다. 즉, 테스트 코드도 리펙터링 해야한다.
이중 표준
- 테스트 API 코드에 적용하는 표준은 실제 코드에 적용하는 표준과 확실히 다르다.
- 테스트 코드는 테스트 환경에서 돌아가는 코드이므로 실제 코드만큼 효율적일 필요는 없다.
- 성능보다는 가독성에 많은 투자를 하라.
//나쁜 예시 - 여러 개의 assert문과 각 assert문에서 호출되는 함수들 때문에 읽기 어렵다.
@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());
}
//개선 - wayTooCold함수로 캡슐화하여 외부 코드를 숨기고 getState의 문자열 의미만 알면 쉽게 이해할 수 있게 하였음.
@Test
public void turnOnLoTempAlarmAtThreshold() throws Exception {
wayTooCold();
assertEquals("HBchL", hw.getState());
}
//getState함수 - 컴퓨터 자원과 메모리가 제한적인 실제 환경에서는 StringBuffer가 좋을 수 있으나 테스트 환경은 그렇지 않다. 가독성이 좋은게 우선이다.
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;
}
테스트 당 개념 하나
- JUnit으로 테스트 코드를 짤 때는 가급적이면 함수마다 assert 문을 단 하나만 사용해야 한다.
- assert 문이 단 하나인 함수는 결론이 하나라서 코드를 이해하기 쉽고 빠르다.
- 함수 하나에 여러 assert 문을 넣어도 된다. 하지만 assert 문 개수는 줄일수록 좋다.
//testGetPageHieratchyAsXml를 테스트 단위로 분리
public void testGetPageHierarchyAsXml() throws Exception {
givenPages("PageOne", "PageOne.ChildOne", "PageTwo");
whenRequestIsIssued("root", "types:pages");
thenResponseShouldBeXML();
}
public void testGetPageHierarchyHasRightTags() throws Exception {
givenPages("PageOne", "PageOne.ChildOne", "PageTwo");
whenRequestIsIssued("root", "types:pages");
thenResponseShouldContain("<name>PageOne</name>", "<name>PageTwo</name>", "<name>ChildOne</name>");
}
//TEMPLATE METHOD 패턴을 사용 - 중복을 제거할 수 있지만 테스트를 이해하기 위해 이곳저곳 찾게 만든다.
@Before
public void giveCondition(){
givenPages("PageOne", "PageOne.ChildOne", "PageTwo");
whenRequestIsIssued("root", "types:pages");
}
public void testGetPageHierarchyAsXml() throws Exception {
giveCondition();
thenResponseShouldBeXML();
}
public void testGetPageHierarchyHasRightTags() throws Exception {
giveCondition();
thenResponseShouldContain("<name>PageOne</name>", "<name>PageTwo</name>", "<name>ChildOne</name>");
}
FIRST
- 깨끗한 테스트는 다음 다섯가지 규칙을 따른다.
- Fast : 테스트는 빨리 돌아야 한다. 테스트가 느리면 자주 돌림 엄두가 나지 않고, 초반에 문제를 찾아 고치지 못한다. 결과적으로 코드 품질이 망가지게 된다.
- Independent : 각 테스트는 서로 의존하면 안된다. 각 테스트는 독립적으로, 어떤 순서로 실행해도 괜찮아야 한다.
- Repeatable : 테스트는 어떤 환경에서도 반복 가능해야 한다. 테스트가 돌아가지 않는 환경이 하나라도 있다면 테스트가 실패할 이유를 둘러댈 변명이 생긴다.
- Self-Validating : 테스트는 성공 아니면 실패로 결과를 내야 한다. 통과 여부를 알려고 로그 파일을 읽게 만들어서는 안된다. 테스트가 스스로 성공과 실패를 가늠하지 않는다면 판단은 주관적이 되며 지루한 수작업 평가가 필요하게 된다.
- Timely : 테스트는 적시에 작성해야 한다. 단위 테스트는 테스트하려는 실제 코드를 구현하기 직전에 구현한다. 실제 코드를 구현한 다음에 테스트 코드를 만들면 테스트가 어렵도록 실제 코드를 설계할 수도 있다.
정리
- 테스트 코드는 실제 코드만큼이나 중요하다.
- 테스트 코드는 실제 코드의 유연성, 유지보수성, 재사용성을 보존하고 강화한다.
- 테스트 코드는 지속적으로 깨끗하게 관리해야 한다.
- 테스트 API를 구현해 도메인 특화 언어(DSL)을 만들면 테스트 코드를 짜기 쉬워진다.
- assert문은 적을수록 코드가 읽기 쉽고 빨라진다.
- FIRST의 규칙을 따르는 깨끗한 테스트 코드를 작성하자.
'개발서적 > 클린 코드' 카테고리의 다른 글
[클린코드] 10장 클래스 (0) | 2021.07.29 |
---|---|
[클린코드] 8장 경계 (0) | 2021.07.27 |
[클린 코드] 7장 오류 처리 (0) | 2021.07.26 |