테스트 코드를 항상 깨끗하게 유지하자. 이를 위해서는 단위테스트 필수
이렇게 테스트 코드를 작성하게 되면 양이 실제 코드만큼 방대해지기 때문에 관리가 중요하다.
실제의 코드가 변하면 테스트 코드도 함께 변화해야 하기 때문에 올바른 테스트 코드를 짜는 것은 중요하다.
유연성, 유지보수성, 재사용성을 제공하는 부분이 바로 단위 테스트다. 테스트 케이스가 존재한다면 변경을 하기 쉬워진다. 따라서 실제 코드를 점검할 수 있는 자동화된 단위 테스트 슈트는 기존 설계를 더 안전하게 보존 가능하다.
##### 테스트 케이스 (Test Case) : 해당 테스트 유닛에 들어가는 Input. 또는 해당 테스트의 수행 n번. (함수 호출)
##### 테스트 유닛 (Test Unit) : 테스트를 하는 단위 기능. (함수)
##### 테스트 슈트 (Test Suites) : 테스트 유닛의 집합. (함수 묶음)
깨끗한 테스트 코드를 만들기 위해서는 가독성이 중요하다. 이를 위해 명료성, 단순성, 풍부한 표현력이 필요하다. 테스트 코드는 최소의 표현으로 많은 것을 나타내야 한다.
'BUILD-OPERATE-CHECK 패턴' 은 가독성을 늘리기에 좋다.
💻 예시
흔히 쓰는 시스템 조작 API를 사용하는 대신 API 위에 함수와 유틸리티를 구현한 후 그 함수와 유틸리티를 사용하므로 테스트 코드를 짜기도 읽기도 쉬워진다. 이렇게 구현한 유틸리티는 테스트 코드에서 사용하는 특수 API가 된다. 즉, 테스트를 구현하는 당사자와 나중에 테스트를 읽어볼 독자를 도와주는 테스트 언어이다.
??DSL에는 java가 포함되나,,? 도메인 특화 언어는 일반적으로 Java, C, Ruby 등의 범용 언어보다 덜 복잡합니다 이런말도 있고
이런말도 있는데,,어떤게 맞는건지 잘 모르겠다..!
테스트 API코드에 적용하는 표준은 실제 코드에 적용하는 표준과 확실히 다르다.
단순하고, 간결하고, 표현력이 풍부해야 하지만, 실제 코드만큼 효율적일 필요는 없다. 실제 환경이 아니라 테스트 환경에서 돌아가는 코드이기 때문인데, 실제 환경과 테스트 환경은 요구사항이 다르다.
💻 Bad Code
우리가 알고싶은 사실은 '급강하 여부' 이다.
그러나 아래의 코드에는 세세한 내용이 너무 많다.@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()); }
💻 Refactoring Code
위의 코드를 리팩터링을 거쳐 이해하기 쉬운 형태로 변환했다.
@Test public void turnOnLoTempAlarmAtThreshold() throws Exception { wayTooCold(); assertEquals("HBchL", hw.getState()); }
💻 More Selection
아래와 같이 구현하게되면 코드 이해도가 올라간다.
@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()); }
실제 환경에서는 절대로 안 되지만 테스트 환경에서는 전혀 문제없는 방식이 있다. 대개 메모리나 CPU 효율과 관련 있는 경우다. 코드의 깨끗함과는 철저히 무관하다.
assert문이 하나인 함수는 결론이 하나이기 때문에 코드를 빠르고 쉽게 이해 가능하다.
Give-When-Then 패턴 이용하여 테스트를 2개로 나눠 각각 assert문을 수행할 경우
public void testGetPageHierarchyAsXml() throws Exception {
givenPages("PageOne", "PageOne.ChildOne", "PageTwo");
whenRequestIsIssued("root", "type:pages");
thenResponseShouldBeXML();
}
public void testGetPageHierarchyHasRightTags() throws Exception {
givenPages("PageOne", "PageOne.ChildOne", "PageTwo");
whenRequestIsIssued("root", "type:pages");
thenResponseShouldContain(
"<name>PageOne</name>", "<name>PageTwo</name>", "<name>ChildOne</name>"
);
}
장점 : 코드 가독성 증가 / 단점 : 중복 코드 증가
Template method 패턴 사용할 경우 중복 제거가 가능하다
단, assert 문의 개수는 최소화 할수록 좋다.
개념이 많이 섞이게되면 어떤 테스트를 진행하는지 파악하는데 있어서 이해하기 힘들뿐만 아니라 유지보수도 하기 힘들다.
F.I.R.S.T | 설명 |
---|---|
빠르게(Fast) | 테스트는 빨라야 한다. 테스트는 자주 돌려 초반에 문제를 찾아낸다. |
독립적으로(Independent) | 각 테스트는 의존하면 안 된다. 각 테스트는 독립적이며, 어떤 순서로 실행해도 괜찮아야 한다. |
반복가능하게(Repeatabel) | 어떤 환경에서도 반복 가능해야 한다. |
자가검증하는(Self-Validating) | 테스트는 bool 값으로 결과를 내야 한다. |
적시에(Timely) | 테스트는 적시에 작성해야 한다. 단위 테스트는 테스트하려는 실제 코드를 구현하기 직전에 구현한다. |
?여기서 적시에 부ㅡ분이 내가 의문을 가졌던 부분과 이어지는것인가,,?