2022-02-17-TIL
2022-02-17-TIL
Today I Learned
Test Coverage
테스트 커버리지는 소프트웨어의 기능적인 품질을 보장하는 테스트 타입이다. 이는 요구사항 명세서나 다른 요구되는 문서들을 포함한다. 테스트 커버리지도 코드 커버리지와 마찬가지로 몇가지 테스팅 메커니즘이 있다. 그러나 어떤 테스트 적용 범위가 중요한지는 비즈니스 제안서를 기반으로 한다. 그 종류는 단위 테스트, 기능 테스트, 통합 테스트, 인수 테스트가 있다.
두 용어를 구분하면 이렇다. 코드 커버리지는 테스트를 실행했을 때 코드의 어느 부분이 실행되었는지를 재는 정량 지표이고, 도구가 자동으로 측정한다. 테스트 커버리지는 요구사항과 기능 중 얼마나 많은 부분을 테스트가 검증하는지를 보는 관점으로, 요구사항 문서를 기준으로 판단한다. 코드 커버리지가 100%여도 요구사항 하나를 아예 구현하지 않았다면 테스트 커버리지는 부족한 것이다.
- https://blog.jankosmala.eu/2021/01/12/unit-tests-code-coverage-line-coverage-vs-branch-coverage/
- https://err0rcode7.github.io/backend/2021/05/11/%ED%85%8C%EC%8A%A4%ED%8A%B8%EC%BB%A4%EB%B2%84%EB%A6%AC%EC%A7%80.html
- https://www.codegrip.tech/productivity/test-coverage-v-s-code-coverage/
Code Coverage
코드 커버리지는 측정 단위에 따라 나뉜다.
- 구문(statement, line) 커버리지: 실행된 코드 줄의 비율이다.
- 분기(branch, decision) 커버리지:
if,switch같은 분기의 참과 거짓 경로가 각각 실행되었는지 본다. - 조건(condition) 커버리지:
a && b처럼 분기 안의 개별 조건 각각이 참과 거짓을 모두 가져 봤는지 본다.
1
2
3
if (age >= 19 && hasTicket) {
enter();
}
age = 20, hasTicket = true 한 경우만 테스트해도 라인 커버리지는 100%지만, 조건이 거짓인 경로는 실행되지 않았으므로 분기 커버리지는 50%다. 그래서 라인 커버리지만 보면 검증하지 않은 분기를 놓치기 쉽다. Atlassian 글이 강조하듯 커버리지 수치는 테스트되지 않은 곳을 찾는 도구이지, 높다고 해서 테스트가 좋다는 보장은 아니다.
- https://tecoble.techcourse.co.kr/post/2020-10-24-code-coverage/
- https://linearb.io/blog/what-is-branch-coverage/
- https://mapository.tistory.com/entry/%EC%BD%94%EB%93%9C-%EC%BB%A4%EB%B2%84%EB%A6%AC%EC%A7%80
- https://www.atlassian.com/continuous-delivery/software-testing/code-coverage
- https://blog.naver.com/PostView.naver?isHttpsRedirect=true&blogId=genycho&logNo=60068626587
- https://blog.jankosmala.eu/2021/01/12/unit-tests-code-coverage-line-coverage-vs-branch-coverage/
Junit5
Vogella의 JUnit 5 튜토리얼은 테스트 메서드와 생명주기 애너테이션(@BeforeEach, @AfterEach, @BeforeAll), 단언(assertEquals, assertThrows, assertAll), 파라미터화 테스트, 테스트 비활성화와 태그 같은 기능을 예제로 정리한다.
- https://www.vogella.com/tutorials/JUnit/article.html
Jacoco
JaCoCo는 Java 코드 커버리지를 측정하는 도구로, 테스트 실행 중 어떤 라인과 분기가 실행됐는지 기록해 리포트를 만든다. 우아한형제들 기술 블로그 글은 Gradle 프로젝트에 JaCoCo를 붙여 리포트를 생성하고, 커버리지 기준을 정해 기준 미달이면 빌드를 실패시키는 설정과, 롬복이 만든 코드나 설정 클래스처럼 측정할 필요가 없는 대상을 제외하는 방법을 다룬다.
- https://techblog.woowahan.com/2661/
REST tutorial with Spring
Spring 공식 튜토리얼로, CRUD 컨트롤러에서 시작해 상태 코드와 예외 처리를 다듬고 Spring HATEOAS로 응답에 링크를 넣는 단계까지 진행한다.
- https://spring.io/guides/tutorials/rest/
Java Custom Exception vs Runtime Exception
Exception을 상속하면 checked 예외가 되어 호출하는 쪽이 반드시 처리하거나 throws로 선언해야 하고, RuntimeException을 상속하면 unchecked 예외가 되어 처리가 강제되지 않는다. Java 튜토리얼의 기준은 호출한 쪽이 예외에서 합리적으로 복구할 수 있으면 checked, 복구할 수 없으면 unchecked다. 비즈니스 규칙 위반을 표현하는 커스텀 예외는 대부분 상위 계층(@ControllerAdvice)에서 일괄 처리하므로 RuntimeException을 상속하는 경우가 많다. Stackify 글은 커스텀 예외를 만들 때 표준 예외로 충분하지 않은 경우에만 만들고, 원인 예외(cause)를 생성자로 받아 넘기며, 이름을 Exception으로 끝내라고 권한다.
- https://stackify.com/java-custom-exceptions/
- https://stackoverflow.com/questions/19857008/extending-exception-runtimeexception-in-java
Java Annotation
애너테이션은 코드에 붙이는 메타데이터다. 직접 정의할 때는 @interface로 선언하고, 메타 애너테이션으로 동작을 정한다. @Retention은 애너테이션 정보를 언제까지 유지할지(SOURCE, CLASS, RUNTIME)를, @Target은 어디에 붙일 수 있는지(클래스, 메서드, 필드)를 정한다. 런타임에 리플렉션으로 읽으려면 RUNTIME이어야 하고, Spring이나 JPA의 애너테이션이 이 방식으로 동작한다.
- https://hirlawldo.tistory.com/43
- https://honeyinfo7.tistory.com/56
Enum
Java 튜토리얼은 enum을 미리 정한 상수 집합을 변수 값으로 쓰게 하는 특별한 데이터 타입으로 설명한다. Java의 enum은 클래스라서 필드, 생성자, 메서드를 가질 수 있고, 컴파일러가 모든 상수를 선언 순서대로 담은 배열을 돌려주는 정적 메서드 values()를 자동으로 추가한다.
- https://docs.oracle.com/javase/tutorial/java/javaOO/enum.html
javax.persistaence.Entity vs org.hibernate.annotations.Entity
엔티티를 선언할 때는 JPA 표준인 javax.persistence.Entity를 쓴다. org.hibernate.annotations.Entity는 표준 @Entity와 함께 붙여 Hibernate 고유 옵션(변경된 컬럼만 UPDATE하는 dynamicUpdate 등)을 지정하던 보조 애너테이션이고, 이후 deprecated되어 @DynamicUpdate, @DynamicInsert 같은 개별 애너테이션으로 대체되었다. 표준 애너테이션을 쓰면 JPA 구현체를 바꿀 여지도 남는다.
- https://rutgo-letsgo.tistory.com/186
- https://stackoverflow.com/questions/700558/org-hibernate-annotations-vs-javax-persistence
Java Transient
Java 키워드 transient가 붙은 필드는 Serializable 객체를 직렬화할 때 제외되고, 역직렬화하면 기본값(null, 0)으로 채워진다. 비밀번호처럼 직렬화하면 안 되는 값이나, 다시 계산할 수 있는 캐시 값에 쓴다. JPA의 @Transient(DB 매핑 제외)와는 목적이 다른 별개의 기능이다.
- https://nesoy.github.io/articles/2018-06/Java-transient
ConcurrentHashMap, SynchronizedHashMap, HashTable
셋 다 스레드 안전한 맵이지만 방식이 다르다.
| 구분 | 동기화 방식 | null 허용 | 순회 중 수정 |
|---|---|---|---|
Hashtable | 모든 메서드가 synchronized | 키, 값 모두 불가 | fail-fast |
Collections.synchronizedMap | 감싼 맵 전체에 락 하나 | 감싼 맵을 따름 | 순회할 때 직접 동기화해야 함 |
ConcurrentHashMap | 조회는 락 없이, 갱신은 세분화된 단위로 | 키, 값 모두 불가 | 예외 없이 약한 일관성 |
Collections.synchronizedMap Javadoc은 반환된 맵을 순회할 때 사용자가 맵에 대해 직접 동기화해야 한다고 명시한다. ConcurrentHashMap Javadoc은 조회 연산이 락을 걸지 않고, 반복자가 ConcurrentModificationException을 던지지 않는다고 설명한다. 그래서 여러 스레드가 동시에 많이 읽고 쓰는 상황에서는 ConcurrentHashMap이 기본 선택이고, Hashtable은 레거시 클래스다.
- https://www.geeksforgeeks.org/difference-between-concurrenthashmap-and-synchronizedhashmap/
- https://javaconceptoftheday.com/synchronized-hashmap-vs-hashtable-vs-concurrenthashmap-in-java/
@Async in Spring
설정 클래스에 @EnableAsync를 붙이면 @Async가 붙은 메서드가 호출한 스레드가 아닌 별도 스레드 풀에서 실행된다. 반환형은 void나 CompletableFuture(Future)로 한다. 프록시 기반이라 같은 클래스 안에서 자기 메서드를 호출하면 비동기로 실행되지 않고, void 메서드에서 난 예외는 호출한 쪽으로 전달되지 않으므로 AsyncUncaughtExceptionHandler로 처리한다. 스레드 풀은 ThreadPoolTaskExecutor 빈으로 크기와 큐를 직접 정하는 편이 안전하다.
- https://www.baeldung.com/spring-async
댓글
아직 댓글이 없습니다