[번역글] F.I.R.S.T. 단위테스트 작성하는 방법.
일류 개발자는 FIRST를 사용하여 테스트 우선적으로 코드를 작성합니다. 테스트는 빠르고 독립적이며 반복 가능하고 자체 검증이 가능하고 시기적절해야 합니다.
단위테스트는 코드의 일부분을 테스트하기 위해 필요합니다. 자바에서는 대부분 클래스일 것입니다. 테스트는 프로그래머로 하여금 자신의 코드에 확신을 갖게 함은 물론 변경이 가능합니다. 개발하는 동안 테스트를 진행함으로써 프로덕션 코드가 수정되거나 테스트를 통과하도록 변경되는지 여부에 관계없이 테스트를 방해하는 모든 변경사항을 재평가할 수 있습니다. 이 포스팅에서는 언클 밥 마틴이 쓴 Clean Code 에 정의된 FIRST 규칙에 대해 설명합니다.
Fast : 테스트는 사용하는데 있어 주저함이 없도록 빨라야합니다. 몇가지 테스트가 있는 메소드 혹은 클래스를 수정하는 경우, 실행하는데 약 1초가 걸리면 변경 후 해당 테스트를 실행할 가능성이 훨씬 더 높습니다. 초기화할 수 있지만 각 테스트 케이스는 실행하는데 1초 이상 걸릴 수도 있습니다. 예를 들어, 20초 이상이 걸리는 통합테스트를 진행한다고 했을 때, 코드 수정이 일어나는 경우 해당 테스트를 실행하기 몹시 싫어질 것입니다. 그러나 단위테스트는 통합테스트와는 달리 여러 구성요소를 테스트할 필요가 없으므로 1초 미만으로 실행하는 것이 가능합니다.
Independent: 테스트는 이전 테스트의 상태에 영향을 받지 말아야합니다. 객체 상태인지 mock 메소드 이어도 마찬가지입니다. 이렇게 한다면 테스트를 개별적으로 실행이 가능합니다. 테스트가 중단되면 메소드로 디버깅하고 무엇이 잘못되었는지 확인하기 전에 다른 테스트를 실행할 필요가 없습니다. 일부 데이터의 존재를 찾는 테스트가 있는 경우 해당 데이터는 테스트 설정에서 생성되어야하며 이후 테스트에 영향을 미치지 않도록 가급적 나중에 제거해야 합니다.
Repeatable : 테스트는 다양한 결과 없이 모든 환경에서 반복 가능해야 합니다. 네트워크나 데이터베이스에 의존하지 않는 경우 의존하는 유일한 것은 테스트중인 클래스 혹은 메소드의 코드이기 때문에 테스트 실패가 가능한 원인을 제거해야 합니다. 테스트가 실패하면 메소드가 올바르게 작동하지 않거나 테스트가 잘못설정됩니다.
Self-Validating : 각각의 테스트는 실패 혹은 성공의 boolean 값을 내놓아야 한다. 테스트가 실행되는 시간동안 해당 테스트의 결과가 옳은지에 대해서는 여러분이 판단하지 않습니다. 테스트의 결과가 옳은지 아닌지를 가려줍니다. 대부분 assertTrue 혹은 assertEquals와 같은 결과에 대하여 성공 혹은 실패라는 결과를 주는 메소드에 의해서 결정됩니다. (최근 JUnit5로 개발할 때는 assertThat을 많이 활용합니다.)
Timely : 단위테스트는 테스트를 통과하는 프로덕션 코드가 작성되기 이전에 작성되어야 합니다. 이것은 TDD(Test Driven Development)를 수행하는 경우에는 따라야하지만 그렇지 않은 경우는 적용되지 않을 수 있습니다. 저는 개인적으로 TDD를 사용하지 않기 때문에 항상 프로덕션 코드를 작성한 이후에 테스트 코드를 작성합니다. 따라야 하는 이유는 알겠지만, 적절하지 않으면 건너뛸 수 있는 규칙이라고 생각합니다.
아래는 위의 규칙을 준수하는 작은 단위테스트입니다. 이것은 단순한 테스트일 뿐이며, 테스트되지 않는 부분이 있다고 보인다면 충분히 맞습니다.
@RunWith(JUnit4.class)
public class PersonValidatorTest {
private PersonValidator testSubject = new PersonValidator();
private Person person;
@Test
public void personCalledDanNewtonIsNotValid() {
person = new Person("Dan","Newton",23,1000);
assertFalse(testSubject.isValid(person));
}
@Test
public void personNotCalledDanNewtonIsValid() {
person = new Person("Bob","Martin",60,170);
assertTrue(testSubject.isValid(person));
}
@Test
public void personIsOlderThanTwentyFiveIsValid() {
person = new Person("Bob","Martin",60,170);
assertTrue(testSubject.isValid(person));
}
@Test
public void personIsYoungerThanTwentyFiveIsNotValid() {
person = new Person("Bob","Martin",10,170);
assertFalse(testSubject.isValid(person));
}
@Test
public void personIsTwentyFiveIsNotValid() {
person = new Person("Bob","Martin",25,170);
assertFalse(testSubject.isValid(person));
}
}
public class PersonValidator {
public boolean isValid(final Person person) {
return isNotCalledDanNewton(person) && person.getAge() > 25 && person.getHeight() < 180;
}
private boolean isNotCalledDanNewton(final Person person) {
return !person.getFirstName().equals("Dan") || !person.getLastName().equals("Newton");
}
}
그래서 이 테스트는 FIRST를 따르는가?
- Fast: 많은 행위를 하지 않으므로 빠르게 실행됩니다.
- Independent: 각 테스트는 새로운 사람을 설정하고 테스트에 필요한 모든 파라미터를 통과합니다.
- Repeatable: 해당 테스트는 다른 클래스들에 의존적이거나 네트워크 혹은 데이터베이스 연결이 필요하지 않습니다.
- Self-Validating: 각각의 테스트는 테스트가 통과하는지 아닌지를 판별하는 하나의 단언문을 갖습니다.
- Timely: 실패!! PersonValidator에서 코드를 작성하기 전에 이러한 테스트를 작성하지 않았지만 이것은 TDD를 사용하지 않기 때문입니다.
이 게시물의 FIRST 규칙을 따르면 단위 테스트가 약간 향상됩니다. 좋은 테스트를 만드는 데 필요한 다른 요소가 있기 때문에 이러한 규칙만 따르면 단위 테스트를 완벽하게 만들 수는 없지만 구축할 수 있는 좋은 토대가 될 것입니다.
앞서 언급했듯이, 이 주제에 대한 더 많은 정보를 찾고, 일반적으로 단위 테스트를 작성하는 방법과 훨씬 더 많은 것을 찾을 수 있는 Clean Code 책을 참조하십시오.