반응형
코드 커버리지?
코드 커버리지란 소프트웨어 테스트의 측정 항목 중 하나로, 테스트를 수행했을 때 얼마나 많은 코드가 실행되었는지를 나타내는 지표입니다. 즉, 소스 코드 중에서 얼마나 많은 부분이 테스트 케이스에 의해 실행되었는지를 백분율로 나타내는 것입니다.
코드 커버리지의 종류
- 라인 커버리지 : 소스 코드의 각 라인이 실행되는 비율
- 브랜치 커버리지 : 브랜치 커버리지는 if문, switch문 등의 분기문에서 모든 경우의 수가 테스트되는 비율
- 메소드 커버리지 : 클래스 내의 메소드 중에서 테스트된 메소드의 비율
- 클래스 커버리지 : 소스 코드 내의 모든 클래스 중에서 테스트된 클래스의 비율
왜 사용할까?
- 코드 커버리지는 소프트웨어 개발에서 테스트된 코드의 양을 측정하는 데 사용됩니다. 이는 테스트되지 않은 코드가 얼마나 남았는지, 즉 테스트를 통해 확인되지 않은 버그가 있는지를 파악하는 데 도움이 됩니다.
- 코드 커버리지를 사용하면 개발자들은 자신이 작성한 코드를 얼마나 잘 테스트했는지를 확인할 수 있습니다. 더 나은 코드 커버리지를 달성하면 코드의 안정성과 신뢰성을 높일 수 있으며, 잠재적인 버그를 더 빨리 발견하고 수정할 수 있습니다.
Java 코드 커버리지 도구
JaCoCo
- 실행 속도가 빠르며 정확도가 높아 코드 커버리지 측면에서 우수한 성능을 보입니다.
- XML, CSV, HTML, JSON 등 다양한 형식으로 리포트를 출력할 수 있습니다.
- 레퍼런스가 가장 많다
Cobertura
- 정확도가 높지만 JaCoCo보다는 느린 속도를 보입니다.
- HTML, XML, CSV 등 다양한 형식으로 리포트를 출력할 수 있습니다.
Emma
- JaCoCo, Cobertura에 비해 속도가 느리지만 정확도가 높아 코드 커버리지 측면에서 우수한 성능을 보입니다.
- XML, HTML 등 다양한 형식으로 리포트를 출력할 수 있습니다.
어떤도구를 사용할까 ?
- 처음 기술을 도입하기때문에 레퍼런스가 가장 중요하다고 생각하여 Jacoco를 도입하였다.
Jacoco 적용하기
플러그인 설정하기
build.gradle
- build.gradle의 plugins부분에
id ‘jacoco’
를 추가해준다
plugins {
id 'java'
id 'org.springframework.boot' version '2.7.9'
id 'io.spring.dependency-management' version '1.1.0'
id 'jacoco'
}
JacocoTestReportsTesk 설정
- 테스트 결과를 리포르로 저장해주는 부분을 설정해준다.
- QueryDsl을 사용하지만 코드 커버리지엔 사용하지않기때문에 Q클래스를 제외 해준다.
- 이 외의 dto, global등도 제외해준다.
jacocoTestReport {
//레포트 파일 생성
reports {
html.enabled true
xml.enabled false
csv.enabled true
}
// jacocoReport 에서 q 클래스는 제외
def Qdomains = []
for(qPattern in "**/QA" .. "**/QZ"){
Qdomains.add(qPattern+"*")
}
afterEvaluate {
// 레포트에서 제외 항목 추가
classDirectories.setFrom(files(classDirectories.files.collect {
fileTree(dir: it,
exclude: ["**/*Application*",
"**/_global/*",
"**/dto/*",
"**/redis/*"
] + Qdomains)
}))
}
finalizedBy 'jacocoTestCoverageVerification'
}
jacocoTestCoverageVerification 설정
- 코드 커버리지를 만족하는지 여부를 확인하는 설정이다.
- 최소 커버리지 수준을 설정할수있으며 통과하지 못하면 실패한다.
violationRules
메서드는 커버리지 통과 기준을 설정 한다.
jacocoTestCoverageVerification {
def Qdomains = []
for (qPattern in "*.QA".."*.QZ") { // qPattern = "*.QA","*.QB","*.QC", ... "*.QZ"
Qdomains.add(qPattern + "*")
}
violationRules {
rule {
enabled = true
element = 'CLASS'
// includes = []
limit {
counter = 'BRANCH'
value = 'COVEREDRATIO'
minimum = 0.90
}
limit {
counter = 'LINE'
value = 'COVEREDRATIO'
minimum = 0.80
}
limit {
counter = 'LINE'
value = 'TOTALCOUNT'
maximum = 200
}
excludes = [ "**/*Application*",
"**/_global/*",
"**/dto/*",
"**/redis/*"
] + Qdomains
}
}
}
enabled
- 이 태스크가 활성화되어 있는지 여부를 지정하는 프로퍼티입니다. 기본값은
true
입니다.
- 검증할 코드 요소를 지정합니다. elements 는 클래스, 메서드, 라인 등 코드의 여러 요소들을 나타내며, includes 와 함께 사용됩니다. 예를 들어, elements = ['CLASS'] 는 테스트 대상으로 클래스들만 선택하여 검증을 수행하겠다는 의미입니다.
BUNDLE
: 묶음 커버리지(Bundle Coverage)를 나타냅니다. 이는 프로젝트의 모든 바이트코드 중 얼마나 많이 커버리지가 측정되었는지를 나타냅니다. 이 요소는 대개 프로젝트의 전체적인 커버리지를 확인하기 위해 사용됩니다.CLASS
: 클래스 커버리지(Class Coverage)를 나타냅니다. 이는 프로젝트의 모든 클래스 중 몇 개의 클래스가 테스트되었는지를 나타냅니다.GROUP
: 그룹 커버리지(Group Coverage)를 나타냅니다. 이는 프로젝트의 패키지 구조를 기반으로 각 패키지에 대한 커버리지를 나타냅니다.METHOD
: 메서드 커버리지(Method Coverage)를 나타냅니다. 이는 프로젝트의 모든 메서드 중 몇 개의 메서드가 테스트되었는지를 나타냅니다.PACKAGE
: 패키지 커버리지(Package Coverage)를 나타냅니다. 이는 프로젝트의 모든 패키지 중 몇 개의 패키지가 테스트되었는지를 나타냅니다.SOURCEFILE
: 소스 파일 커버리지(Source File Coverage)를 나타냅니다. 이는 프로젝트의 모든 소스 파일 중 몇 개의 파일이 테스트되었는지를 나타냅니다.
- 파일 경로를 패턴으로 지정합니다. 예를 들어, `include '/service/'`*는
service
패키지의 모든 클래스를 커버리지 검증 대상으로 지정합니다
- 검증할 커버리지 항목을 지정합니다
BRANCH
: 분기 커버리지(Branch Coverage)를 나타냅니다. 이는 코드에서 조건문, switch 문 등의 브랜치(가지)가 얼마나 많이 실행되었는지를 나타냅니다. 즉, 브랜치가 있는 조건문에서 모든 경우의 수를 실행해보았는지 여부를 확인하는 지표입니다.CLASS
: 클래스 커버리지(Class Coverage)를 나타냅니다. 이는 프로젝트의 모든 클래스 중 몇 개의 클래스가 테스트되었는지를 나타냅니다.COMPLEXITY
: 코드 복잡성(Complexity)을 나타냅니다. 이는 코드 내의 제어 흐름(예: if문, loop문 등)이 얼마나 복잡한지를 나타내는 지표입니다.INSTRUCTION
: 명령어 수(Instruction Coverage)를 나타냅니다. 이는 코드의 모든 명령어 중 몇 개가 실행되었는지를 나타냅니다.LINE
: 라인 커버리지(Line Coverage)를 나타냅니다. 이는 코드에서 얼마나 많은 라인이 테스트되었는지를 나타냅니다.METHOD
: 메서드 커버리지(Method Coverage)를 나타냅니다. 이는 프로젝트의 모든 메서드 중 몇 개의 메서드가 테스트되었는지를 나타냅니다.
- 검증할 커버리지 값의 범위를 지정합니다.
COVEREDCOUNT
: 커버된 항목의 수를 나타냅니다. 예를 들어,COVEREDCOUNT
가 10인 경우, 해당 항목에 대해 10개의 코드 라인이 테스트를 통과했다는 것을 의미합니다.COVEREDRATIO
: 커버된 항목의 비율을 나타냅니다. 예를 들어,COVEREDRATIO
가 80%인 경우, 해당 항목의 코드 라인 중 80%가 테스트를 통과했다는 것을 의미합니다.MISSEDCOUNT
: 커버되지 않은 항목의 수를 나타냅니다. 예를 들어,MISSEDCOUNT
가 5인 경우, 해당 항목에 대해 5개의 코드 라인이 테스트를 통과하지 못했다는 것을 의미합니다.MISSEDRATIO
: 커버되지 않은 항목의 비율을 나타냅니다. 예를 들어,MISSEDRATIO
가 20%인 경우, 해당 항목의 코드 라인 중 20%가 테스트를 통과하지 못했다는 것을 의미합니다.TOTALCOUNT
: 전체 항목의 수를 나타냅니다. 예를 들어,TOTALCOUNT
가 20인 경우, 해당 항목에 대해 총 20개의 코드 라인이 존재한다는 것을 의미합니다.
minimum
- 테스트 코드 커버리지에 대한 최소 기준값을 설정하는 데 사용됩니다. 이 옵션은 검증할 코드 커버리지 결과의 최소 요구 값을 지정하여 테스트가 통과되는지 여부를 결정합니다.
exclude
- 코드 커버리지 검증에서 제외할 파일을 지정하는 데 사용되는 옵션입니다. 이 옵션은 파일 경로를 패턴으로 지정합니다.
- 예를 들어, `exclude '/Test'
**는 이름이 **
Test`**로 끝나는 모든 클래스를 검증 대상에서 제외합니다. 이렇게 하면 테스트 코드나 mock 클래스 같이 실제로 실행되지 않는 코드를 코드 커버리지 검증에서 제외할 수 있습니다.
테스트 진행
- 테스트 코드를 작성하고 Jacoco로 확인을 해보자
테스트 코드 작성
@ExtendWith(MockitoExtension.class)
@DisplayName("유저 테스트")
class UserServiceTest {
@InjectMocks
private UserService userService;
@Mock
private UserRepository userRepository;
@Mock
private PasswordEncoder passwordEncoder;
@Test
@DisplayName("로그인-성공")
void loginTest() {
LoginRequestDto loginRequestDto = LoginRequestDto.of("test1", "123123");
HttpServletResponse responseMock = mock(HttpServletResponse.class);
User user = User.of("test1", "1234");
when(userRepository.findByUserId(any())).thenReturn(Optional.of(user));
when(passwordEncoder.matches(any(), any())).thenReturn(true);
userService.login(loginRequestDto, responseMock);
}
@Test
@DisplayName("로그인-아이디-실패")
void loginFailIdTest() {
LoginRequestDto loginRequestDto = LoginRequestDto.of("test1", "123123");
HttpServletResponse responseMock = mock(HttpServletResponse.class);
when(userRepository.findByUserId(any())).thenReturn(Optional.empty());
Exception exception = assertThrows(CustomException.class, () ->
userService.login(loginRequestDto, responseMock)
);
assertEquals(exception.getMessage(), ErrorType.NOT_MATCHING_INFO.getMsg());
}
@Test
@DisplayName("로그인-패스워드-실패")
void loginFailPwTest() {
LoginRequestDto loginRequestDto = LoginRequestDto.of("test1", "123123");
HttpServletResponse responseMock = mock(HttpServletResponse.class);
User user = User.of("test1", passwordEncoder.encode("5678"));
when(userRepository.findByUserId(any())).thenReturn(Optional.of(user));
Exception exception = assertThrows(CustomException.class, () -> {
userService.login(loginRequestDto, responseMock);
});
assertEquals(exception.getMessage(), ErrorType.NOT_MATCHING_INFO.getMsg());
}
}
테스트 실행
- test를 꼭 실행 후 jacocoTestReport를 실행해야한다
- 아래와 같은 화면을 만난다면 위에서 설정한 조건을 만족하지 못했을 경우 볼 수 있다.
- 테스트가 성공했다면
경로\build\reports\jacoco\test\html\index.html에서 테스트 커버리지를 확인 가능하다.
붉은색으로 표기된 항목은 테스트를 작성하지 않은 코드이고
초록색은 테스트를 작성한 코드로
아래 사진 처럼 확인이 가능하다!
참조
반응형
'공부 > JAVA SPRING' 카테고리의 다른 글
[Swagger] 스웨거 오류 Parser error on line 2unexpected end of the stream within a flow collection (0) | 2024.06.28 |
---|---|
[Java] Mybatis 동시성 제어 (0) | 2023.09.27 |
[SpringBoot] QueryDSL 사용법 (0) | 2023.03.24 |
[SpringBoot] Kakao REST API 검색어로 위도 경도 좌표 받기 (0) | 2023.03.13 |
[SpringBoot] TMDB에서 API 파싱(RestTemplate, WebClient차이) (0) | 2023.03.03 |
댓글