개발을 하다보면 내 코드가 잘 작동하고 있는지 , 버그가 없는지 확인하고 싶을 때 어떻게 테스트를 하면 좋을까 라는 생각을 하게됩니다.
테스트 범위에 따라 분류하면 단위 테스트
, 통합 테스트
, 기능 테스트
로 나눠 볼 수 있습니다.
단위 테스트는 전체 코드 중 작은 부분
을 테스트하는 것을 말합니다. 다시 말하면 하나의 모듈을 기준으로 독립적으로 진행되는 가장 작은 단위의 테스트입니다. 여기서 모듈은 애플리케이션에서 작동하는 하나의 기능 또는 메서드
라고 이해할 수 있습니다.
만약 테스트에 네트워크나 데이터베이스 같은 외부 리소스가 포함된다면 그것은 단위 테스트가 아닙니다. 즉 단위 테스트는 애플리케이션을 구성하는 하나의 기능이 올바르게 동작하는지 독립적으로 테스트하는 것입니다.
예를 들면.. 한 함수에 입력 값을 주어서 그에 대한 함수의 출력값이 정확한가..?
반면에 통합 테스트는 캐시나 데이터베이스 등 다른 컴포넌트에 연결을 해야 하고, 시스템을 구성하는 컴포넌트들이 많아질수록 테스트를 위한 비용이 커지는 반면 , 단위테스트는 해당 부분만 독립적으로 테스트하기 때문에 어떤 코드를 리팩토링해도 빠르게 문제 여부를 확인할 수 있습니다.
그 외에 단위 테스트의 장점으로는 다음이 있습니다.
- 테스팅에 대한 시간과 비용을 절감할 수 있습니다.
- 새로운 기능 추가 시에 수시로 빠르게 테스트 할 수 있습니다.
- 리팩토링 시에 안정성을 확보할 수 있습니다.
- 코드에 대한 문서가 될 수 있습니다.
그렇기 때문에 실무에서 단위 테스트를 선호하며, 요즘 TDD ( Test-Driven Development ) 에서의 테스트는 단위 테스트를 의미합니다.
단위 테스트는 간단하고 명확해야 합니다. 만약 코드의 설계가 별로 좋지 못하다면 단위 테스트를 작성하기 어렵겠죠. 따라서 작은 단위로 테스트 코드를 작성하는 단위 테스트는 좀 더 나은 코드를 만들 수 있게 도와줍니다.
단위 테스트를 사용한다면 좋은 코드를 디자인할 수 있을 뿐만 아니라 어떤 메서드에 변화가 생겼을 때 그 함수가 안전하게 수행되는지 보장
하고 , 그 함수를 다른 테스트에서도 적용하기 쉽게 만들어줍니다.
그리고 단위 테스트는 빈번히 일어나는 버그를 막는데도 뛰어난 역할
을 합니다. 코드 일부분에 버그가 발생했을 경우 어떤 부분에서 문제가 생겼는지 빠르게 해결
할 수 있습니다.
그래서 통합 테스트 ( Integration Test) , 와 기능 테스트 ( Functional Test) 는 이벤트의 흐름에 이상이 없는지 테스트를 한다면, 단위 테스트는 어떤 부분에 문제가 있고 어디를 고쳐야 하는지 명확하게 해줍니다.
마지막으로 테스트 중심 개발 ( Test-driven development ) 일 때 단위 테스트는 꼭 작성해야 하며, 코드의 디자인을 개선하고 나중에 코드의 리팩토링을 효율적으로 할 수 있습니다.
Spring에서는 단위 테스트를 주로 JUnit5
으로 테스트를 합니다.
@Test
public void moveCar() {
//given
Car car = new Car();
// when
car.move();
// then
assertThat(car.getStatus()).isEqualTo(1);
}
최근 단위 테스트는 given-when-then 패턴으로 작성하는 추세로 , given-when-then 패턴이란 1개의 단위 테스트를 3가지 단계로 나누어 처리하는 패턴으로 각각의 단계는 다음과 같습니다.
어떤 객체가 자체적으로 모든 일을 처리하면 좋겠지만.. 일반적인 애플리케이션에서는 1개의 기능을 처리하기 위해 다른 객체들과 메세지를 주고 받아야 합니다. 하지만 앞서 설명했듯 단위 테스트는 해당 모듈에 대한 독립적인 테스트이기 때문에 다른 객체와 메세지를 주고 받는 경우에 문제가 발생합니다. 이때 다른 객체 대신에 가짜 객체 ( Mock Object )
를 주입하여 어떤 결과를 반환하라고 정해진 답변을 준비시켜야 하는데 이를 stub
이라고 합니다.
예를 들어 데이터베이스에 새로운 데이터를 추가하는 코드를 테스트한다고 하면, 가짜 데이터베이스 ( Mock Database ) 를 주입하여 insert 처리 시에 반드시 1을 반환하도록 하는 것이 stub 입니다.
일반적으로 요구 사항은 계속 변하고, 그에 맞춰 우리의 코드 역시 변경되어야 합니다. 하지만 실제 코드를 변경한다는 것은 잠재적인 버그가 발생할 수 있음을 내포하는데, 좋은 테스트 코드가 있다면 변경된 코드를 검증하여 이를 해결
할 수 있습니다. 또한 실제 코드가 변경되면 테스트 코드도 변경되어야 하는데, 이러한 이유로 테스트 코드를 가독성있게 작성해야 합니다. 따라서 다음과 같이 작성해 주어야합니다.
- 1개의 테스트 함수에 대해 assert를 최소화 합니다.
- 1개의 테스트 함수는 1가지 개념 만들 테스트합니다.
좋고 깨끗한 테스트 코드는 FIRST라는 5가지 규칙을 따릅니다.
- Fast: 테스트는 빠르게 동작하여 자주 돌릴 수 있어야 한다.
- Independent: 각각의 테스트는 독립적이며 서로 의존해서는 안된다.
- Repeatable: 어느 환경에서도 반복 가능해야 한다.
- Self-Validating: 테스트는 성공 또는 실패로 bool 값으로 결과를 내어 자체적으로 검증되어야 한다.
- Timely: 테스트는 적시에 즉, 테스트하려는 실제 코드를 구현하기 직전에 구현해야 한다.
통합 테스트는 이름과 같이 각각의 시스템들이 서로 어떻게 상호작용
하고 제대로 작동하는지 테스트
하는 것을 말합니다. 통합 테스트는 단위 테스트와 비슷하지만, 단위 테스트는 다른 컴포넌트들과 독립적
이라는 큰 차이점이 있습니다. 다시 말해 모듈을 통합하는 과정에서 모듈 간의 호환성을 확인하기 위해 수행되는 테스트입니다.
예를 들면 단위 테스트는 DB에 접근하는 코드는 실제 DB와 통신하는 것은 아니지만 통합 테스트는 실제로 통신을 해야합니다.
통합 테스트는 단위 테스트만으로 충분하다고 느끼지 못할 때 사용합니다. 일반적으로 애플리케이션은 여러 개의 모듈들로 구성이 되고, 모듈들끼리 메세지를 주고 받으면서 ( 함수 호출 ) 기능을 수행합니다. 그렇기 때문에 통합된 다른 모듈들이 올바르게 통신하고 있는지 증명하고 싶을 때
( DB와 잘 통신하고 있는가? ) 사용하는 것이 통합 테스트입니다. 그렇기에 통합 테스트는 독립적인 기능에 대한 테스트가 아니라 웹 페이지로부터 API를 호출
하여 올바르게 동작하는지 확인할 수 있습니다.
그래서 통합 테스트는 단위 테스트를 작성하는 것보다 복잡하고 시간이 오래 걸립니다.
따라서 통합 테스트가 필요한 것이 아니라면 단위 테스트에 집중하는게 좋습니다.
Spring Boot 에서는 클래스 상단에 @SpringBootTest
어노테이션을 붙여 통합 테스트를 수행합니다.
@SpringBootTest
class Project {
@Test
@DisplayName("변조된 RefreshToken 차단")
public void tokenAuthorizationTest() throws Exception {
Assertions.assertThrows(FalsifyTokenException.class , () -> jwtService.validateRefreshToken("thisIsNotValuableToken"));
}
}
코드가 짧다고 유닛 테스트가 아닙니다.. 위의 코드는 두개 이상의 메서드가 상호작용이 잘 되는지 확인하는 코드입니다.
기능 테스트는 E2E 테스트 혹은 브라우저 테스트라고 불리며 모두 같은 의미를 갖고 있습니다. 기능 테스트는 어떤 어플리케이션이 제대로 동작하는지 완전한 기능을 테스트
하는 것을 의미합니다.
예를 들어, 어떤 웹 어플리케이션을 기능 테스트 한다고 하면 특정한 페이지에 접속하는 것을 말합니다.
유닛 테스트를 사용하여 개별의 함수가 제대로 동작하고 통합 테스트를 통해 서로 다른 시스템이 잘 상호작용하는지 확인할 수 있습니다. 이에 비에 기능 테스트는 이와 완전히 다른데 기능테스트는 작성하기 매우 어렵고 복잡
하기 때문에 많은 시간이 걸립니다.
때문에 기능 테스트를 세밀하게 나눠서 사용하면 효율성이 떨어
집니다. 대신 기능 테스트는 사용자와 앱의 상호작용을 테스트할 때 사용합니다. 예를 들어 회원 가입에 대한 기능 테스트라 하면 유저가 회원가입을 마치고 회원 가입 완료 페이지를 출력하는 것을 보장해야 하죠.
JUnit5 만으로 단위 테스트를 충분히 작성할 수 있지만 JUnit5에서 제공하는 assertEquals() 와 같은 메소드는 AssertJ가 주는 메소드에 비해 가독성이 떨어집니다. 그렇기 때문에 순수 Java 어플리케이션에서 단위 테스트를 위해 JUnit5와 AssertJ 조합을 많이 사용합니다.
참고 블로그 1 : https://cjwoov.tistory.com/9
참고 블로그 2 : https://velog.io/@mon99745/%EC%9C%A0%EB%8B%9B-%ED%85%8C%EC%8A%A4%ED%8A%B8Unit-Test-%ED%86%B5%ED%95%A9-%ED%85%8C%EC%8A%A4%ED%8A%B8Integration-Test-%EA%B8%B0%EB%8A%A5-%ED%85%8C%EC%8A%A4%ED%8A%B8Funcional-Test%EB%9E%80
참고 블로그 3 : https://mangkyu.tistory.com/143