오봉이와 함께하는 개발 블로그
CleanCode - 단위 테스트 본문
단위 테스트
이번 내용은 TDD와 테스트 코드 작성하는 방법에 대해 말하는 내용으로 보인다.
TDD 경험이 없기 때문인지 사실 TDD의 효용성에 대해서는 공감하지 못하고 있다. 목표가 불확실할 때(도메인에 대해 완벽하게 알지 못하는 경우) 이런 방법론을 통해 개발할 수 있다는 정도만 알고 있는 상황이다. 내가 직접 설계해야할 일이 있다면 그때 공감할 수 있지 않을까 싶다.
깨끗한 테스트 코드 유지하기
저자의 경험으로 깨끗한 테스트 코드의 필요성을 주장한다.
저자는 지저분한 테스트 코드를 작성해서 정성스럽게 작성하지 않아도 테스트 통과가 되기만 하면 된다는 규칙을 팀에 적용한 결과로 실제 비즈니스 코드 발전에 따라 시간이 흐르며 관리 안 되는 테스트 코드를 결국 폐기했다 한다.
내가 느끼기엔 더러운 테스트 코드는 쓰레기같은 주석과 유사한 성격같다. 다른 점이 있다면 테스트 코드는 수정하지 않으면 폐기를 통해 코드를 돌려야 하지만, 주석은 수정하지 않아도 된다는 점이다.
비즈니스 코드 수정에 따라 또 테스트 코드를 수정해야 하면 테스트 코드 수정에 더 많은 비용이 발생하는 경우가 발생할 것이다.
즉 처음부터 테스트 코드를 잘 작성해야 한다는 점을 우리에게 알려준다.
필요성
나는 테스트 코드를 작성한 경험이 많지 않지만 테스트 코드의 필요성은 알고 있다.
커버리지가 높은 테스트 코드가 있으면 잘못된 설계과 더러운 코드들 리팩토링 하는 시점에 테스트 코드를 통해 두려움 없이 변경할 수 있다.
때문에 한번 작성한 테스트 코드를 통해 리팩토링 후 수동으로 테스트를 하지 않아도 된다.
이게 테스트 코드의 필요성이다. 초기 투자를 통해 더 많은 비용 절감이 가능하다.
또, 테스트 코드를 통해 요구사항을 파악할 수도 있을 것이다.
깨끗한 테스트 코드
깨끗한 테스트 코드를 작성하기 위해서는 첫째 가독성, 둘째 가독성, 셋째도 가독성 가독성이 제일 중요하다고 한다.
BUILD-OPERATE-CHECK 패턴을 통해 작성하면 좋다고 한다.
- BUILD : 테스트 자료(객체) 생성
- OPERATE : 테스트 자료 조작
- CHECK : 결과 확인
어디서 많이 본 기억이 있는데, given-when-then 패턴과 동일해 보인다.
이중표준
@Test
public void turnOnLoTempAlamAtThreashold() throws Exception {
hw.setTemp(WAY_T00_C0LD);
controller.tic();
assertTrue(hw.heaterState());
assertTrue(hw.blowerState());
assertFalse(hw.coolerState());
assertFalse(hw.hiTempAlarm());
assertTrue(hw.loTempAlam());
}
위 코드는 온도가 급격하게 떨어질 경우 경보, 온풍기, 송풍기가 모두 가동되는지 확인하는 코드라고 한다.
assertTrue와 assertFalse가 섞여있어 가독성이 떨어지고 자칫 햇갈릴 수 있다.
@Test
public void turnOnLoTempAlamAtThreashold() throws Exception {
wayTooCold();
assertEquals("HBchL", hw.getState());
}
이런 코드로 리팩토링했다고 한다.HBchL
자체는 사실 알아보기 힘들다. 저자는 적절해 보인다고 하지만 내 개인적인 의견으로 적절하지 않아 보여 다른 방법을 사용하는 것이 좋아보인다.
하지만 적절하다 가정하자.
대문자는 켜짐, 소문자는 꺼짐을 뜻하고 문자는 항상 heater, blower, cooler, hi-temp-alarm, lo-temp-alarm 순서다
이런 식으로 수정하면 여러 케이스에 대해 테스트 작성이 쉬워진다
@Test
public void turnOnCoolerAndBlowerTfTooHot() throws Exception {
tooHot();
assertEquals("hBChl", hw.getState());
}
@Test
public void turnOnHeaterAndBlowerTfTooCold() throws Exception {
tooCold();
assertEquals("HBchl", hw.getState());
}
@Test
public void turnOnHiTempAlamAtThreshold() throws Exception {
wayTooHot();
assertEquals("hBCHL", hw.getState());
}
아래 코드는 getState()에 대한 정보다.
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;
}
여기서 문자열을 더하기 연산을 통해 수행하여 일반적인 PC에서는 문제가 발생하지 않겠지만, 자원이 한정적인 임베디드 시스템에서는 문제가 발생할 수 있다.
기능 개발의 목적이 임베디드 시스템일 경우는 StringBuffer 를 통해 해결할 수 있겠지만 우리가 테스트하는 환경은 자원이 풍부할 확률이 높아 문제되지 않을 것이다.
이게 이중 표준의 본질이라 한다. 실제 환경에선 안 되지만 테스트 환경에서 문제 없는 방식을 사용하는 것
테스트 당 assert 하나
다른건 모르겠지만 내가 느끼기에 가독성이나 테스트 코드의 목적을 위해서라도 assert를 하나로 하는 것 혹은 최대한 줄이는 것이 좋아 보인다.
assert가 적다는 것은 테스트 목적이 하나 혹은 그만큼 테스트 케이스가 적다는 것을 의미한다고 생각한다. 그렇다면 필연적으로 실제 비즈니스 로직도 간결하고 목적이 하나 혹은 목적이 적은 즉, 잘 짜여진 코드라고 생각하는 것이 합리적이지 않을까?
또 assert가 하나라면 OPERATE가 길지 않을 것이기 때문에 그만큼 가독성도 좋을 것이다.
책에서 말하고자 하는 내용이 위 내용이 아닌 거 같지만, 내가 느낀점은 위와 같다.
F.I.R.S.T
Fast(빠르게)
테스트 코드는 빠르게 돌아야 한다. 오래 걸리는 테스트 코드는 자주 돌릴 엄두가 나지 않는다. 자주 돌려야 반복해서 문제를 찾고 수정할 수 있는데 고칠 엄두가 나지 않아 코드 품질이 낮아진다.
Independent(독립적으로)
각 테스트 코드는 의존적이면 안되고 독립적으로 수행되어야 한다.
A, B, C 테스트 코드가 있을 때 B의 테스트를 위해 A가 B에 사용할 무언가를 준비해선 안된다.
A가 실패하면 B도 실패한다. 이런 것이 n개 쌓일 경우 문제 지점 파악이 어렵다.
Repeatable(반복 가능한)
Self-Validating(자가 검증)
테스트는 Bool 값으로 결과를 만들자. 성공 아니면 실패다.
통과 여부를 보려고 로그 파일을 읽거나, 파일을 비교하면 안 된다. 통과 여부를 위해 이를 확인하면 수작업이 필요하게 된다.
테스트는 스스로 성공과 실패를 판단해야 한다.
위 내용을 읽고 개인적인 생각으로 파일을 읽거나, 로그 파일을 작성하거나 하는 등의 작업을 하는 함수에 대해서는 어떻게 적용해야 하는지 모르겠다. 조금 더 공부가 필요할 거 같다.
Timely(적시에)
테스트 코드를 먼저 작성하고 실제 코드를 작성하자.
실제 코드를 작성하고 테스트 코드를 작성하면 실제 코드가 테스트하기 어려운 코드라는 사실을 발견할지도 모른다. 혹은 불가능하다 판단할 수도 있다.
나는 이런 경험을 한 기억이 있다. 실제 코드를 작성 후 테스트 코드를 작성하려 하니 도저히 어떻게 해야 좋을지 감도 안 왔었다. 물론 나의 테스트 코드 작성에 대한 스킬 부족도 있겠지만 아무리 그래도 감이 오지 않을 거 같지는 않은데 감이 오지 않아 어떻게 겨우겨우 돌아가기만 하는 이상한 코드를 만들었던 기억이 있다.
'이론' 카테고리의 다른 글
내 코드가 그렇게 이상한가요? - 조건 분기 : 미궁처럼 복잡한 분기 처리를 무너뜨리는 방법 (1) | 2024.01.09 |
---|---|
내 코드가 그렇게 이상한가요? - 응집도 : 흩어져 있는 것들 (2) | 2024.01.09 |
CleanCode - 경계 (1) | 2024.01.04 |
CleanCode - 오류 처리 (0) | 2024.01.03 |
내 코드가 그렇게 이상한가요? - 컬렉션 : 중첩을 제거하는 구조화 테크닉 (0) | 2024.01.01 |