- 테스트 커버리지 100% 달성기[1] - 레이어별 100% 달성 과정
- 테스트 커버리지 100% 달성기[2] - 테스트 환경 구축 및 시간 단축
- 테스트 커버리지 100% 달성기[3] - 테스트 코드 가독성 개선
1편과 2편에서 커버리지 100% 달성, 테스트 환경 구축, 테스트 코드 성능 개선을 다루었다.
많은 시간 공을 들여 작업을 하다 보니 내가 해왔던 방식이 맞는지 검증도 하고 싶었고, 그동안 다루지 않았던 부분을 개선하고 싶기도 하였다.
무엇 보다 나는 아래 두 가지에 대해 검증 하고 싶었다.
- 커버리지 100%가 prod 코드에 결함이 없다는 것을 보장할 수 있는지
- 가독성 : 테스트 코드가 prod 코드를 잘 설명하고 있는지
정답을 낼 수는 없겠지만 항상 위 두 부분에 부족함이 있다고 전제 하고 이 부분을 리팩토링 하기로 결심하였다.
이를 위해 이펙티브 유닛 테스팅이라는 책을 참조하여 작업 계획을 세웠다.
TC 리팩토링 - todo 리스트
분류 | 내용 |
가독성 | 광역 단언 - 너무 세부적인 내용까지 테스트하여 쉽게 테스트 실패 |
상세 정보의 분류 : 코드가 비대한 경우 논리적으로 구분함(셋업, 단언, 티어다운) | |
다중 인격 테스트 : 하나의 테스트에서 두가지 이상을 테스트 | |
매직 넘버 제거 | |
과잉보호 : 단언문 하나로 실패 성공을 충분히 구분할 수 있는데 무자비하게 많은 단언문 작성된 경우 | |
유지보수 | 중복 |
양치기 테스트 : 간헐적으로 성공 및 실패하는 테스트 | |
절대 경로 제거, 상대 경로 사용 | |
메서드간 응집력 : 사용 테스트 픽스쳐 다르다면 테스트 클래스도 분리 | |
신뢰성 | 주석 |
낮아진 기대치 | |
설계 | SOLID |
private, static 메서드 제거 | |
new는 대체 불가 | |
생성자에 로직 구현 제거 | |
싱글톤 패턴 제거 | |
상속보다 합성 | |
외부 라이브러리 래핑 |
작업 대상을 위와 같이 분류하여 정리하였다.
위 내용에서 낮아진 기대치, 다중 인격 테스트는 잘못 작성되었음에도 불구하고 성공하는 테스트를 걸러낼 수 있다.
커버리지가 100%라고 하더라도 잡아내지 못하는 부분을 파악할 수 있는 내용이다.
이외 상세 정보의 분류, 매직 넘버 제거, 메서드 간 응집력 등은 가독성 향상에 도움이 되는 내용이다.
테스트 코드를 쉽고 빠르게 작성할 수 있는 설계 영역의 내용들도 포함되어있다.
중요하다고 생각되는 부분을 가져왔는데 1편과 2편에서 테스트 환경을 구축하며 포함된 내용들도 더러 있다.
하나씩 설명을 할텐데 개선했던 내용이 있는 경우만 사례를 들며 설명하고 그 외의 경우는 빠르게 넘어가겠다.
1. 가독성
1.1 광역 단언
너무 세부적인 내용까지 테스트하여 쉽게 테스트에 실패하는 것이다.
책에서 소개하는 내용은 로그 파일에 작성된 내용 본문까지 테스트하여 성공 케이스도 실패하는 결과가 나오는 케이스이다.
나의 테스트 코드에서 광역 단언이라고 할 수 있는 부분을 찾아보니 엔티티 조회 결과 테스트라고 생각되었다.
(물론 성공 케이스가 실패 결과를 나타내진 않았지만 너무 세부적인 부분까지 테스트를 했다.)
나의 엔티티 테스트는 db에 저장되어있는 값을 가져와 동치 비교하는 테스트가 더러 작성되어있다.
커버리지 100% 달성을 위해 엔티티 테스트를 억지로 작성한 경향이 있어 어떤 것을 테스트할지 몰라 동치 테스트를 진행했었다.
대게 자바 프로젝트의 경우 롬복 코드를 테스트 커버리지에 제외시켜서 엔티티의 게터 메서드는 제외시킬 수 있지만, 내 프로젝트는 코틀린이기에 getter가 롬복이 아닌 바이트 코드로 생성되어 getter만 제외할 수 없어 테스트를 진행해야했다.
커버리지를 위한 억지 테스트 코드이기는 하지만 책을 읽고 나니 광역 단언이라고 생각되어 동치 비교 테스트는 제거하기로 했다.
DB에 존재하는 값을 제대로 가져오는지는 ORM 프레임워크에서 제공해주는 기능이다.
이를 테스트 하겠다는 것은 프레임워크를 믿지 못해 테스트 하는 것과 같다고 생각한다.
우리는 DB의 테이블 제약조건과 클래스 설계가 일치하는지, 이에 대한 매핑이 정확하게 이뤄지는지에 집중해야한다.
우리가 설계한 코드에 대한 검증을 해야한다.
따라서 나는 동치 비교는 제거하고 데이터를 조회하고 칼럼값이 존재하는지 not null 체크만 진행하였다.
값이 null이면 매핑 검증을 진행할 수 없기에 반드시 존재해야하고 내가 사용하는 querydsl이 데이터 매핑이 올바르지 않은 경우 예외를 터트리기에 충분한 검증이 이뤄질 수 있다고 판단했다.
1.2. 상세 정보의 분류
테스트 코드가 너무 비대한 경우 논리에 가독성이 떨어지므로 논리에 따라 코드를 분류할 필요가 있다.
@Before과 @After를 통해 셋업과 티어다운을 분리하고, 단언문이 너무 긴 경우에도 별도의 private 메서드에 단언문을 모아 가독성을 향상 시킬 수 있다.
1.3 다중 인격 테스트
하나의 테스트에서 여러 관점의 검증을 한번에 진행하는 경우이다.
이 부분은 개인의 주관이 많이 관여되는 영역으로 파악하기 어려운 영역이라고 생각한다.
본인이 생각하기에 하나의 목적인 테스트가 사실은 두 개 이상을 검증할 수 도 있고,
테스트 대상을 명확하게 도출하지 못하고 하나의 테스트로 모든 로직을 수행하게 할 수도 있다.
테스트 코드 작성시 테스트하고자 하는 목적이 한가지로 확실해야하며,
prod 코드를 설계할 때 클래스와 메서드를 책임과 역할에 맞게 분리시키는 능력도 중요하다.
1.4 매직넘버 제거
매직넘버는 쉽게 말해 하드 코딩된 값이다.
코드에 1, 2, 문자열과 같이 아무의미 없이 하드코딩된 값은 테스트 코드를 파악하기 어렵게 만든다.
만일, 1과 2가 아니라 3이나 4로 테스트하면 실패하는지 성공하는지 구분하게 어렵게 만든다.
따라서 이런 케이스는 상수에 이름을 주어 해결하였다.
SUCCESS_ID와 FAIL_ID, EXCEPTION_ID와 같은 이름을 주어 상수에 이름을 주어 가독성 향상 작업을 진행하였다.
또한 아무 의미 없는 값들, 어떤 값을 넣어도 테스트 코드에 영향을 주지 않는 경우라면 any나 whatever과 같은 문자열을 사용하여 코드를 읽는 사람으로 하여금 의미없는 값이라는 걸 알려줄 수 있다.
나 또한 문자열 값의 경우 아무 문자를 집어 넣은 케이스가 많았는데 any라는 문자열을 넣어서 의미 없는 값이라고 명시적으로 표현하는 방식으로 바꾸었다.
1.4 과잉 보호
단언문 하나 또는 몇개로 충분히 검증할 수 있음에도 불구하고, 너무 많은 단언문을 사용하여 검증하는 경우 가독성을 더욱 저하시킬 수 있다.
2. 유지보수
2.1 중복
중복에는 상수 중복, 구조 중복, 코드 중복 등이 존재한다.
상수 중복은 동일한 상수값을 테스트 메서드에서 반복적으로 선언하여 사용하는 케이스이다.
이런 경우 인스턴스 레벨이나 클래스 레벨에서 이름을 부여한 상수로 선언하여 전역적으로 사용함으로써 중복을 제거할 수 있다.
나또한 테스트 더블을 제어하기 위한 목적으로 인스턴스 수준에 필요한 상수와 전역적으로 사용할 상수를 따로 관리하도록 하여 코드 중복을 제거하고 테스트 더블을 일관적으로 동작시키도록 하였다.
예를 들면, FAIL_ID = 2, EXCEPTION_ID =3 와 같이 선언하여 해당 상수를 쓰면 더블이 실패 또는 예외 케이스를 반환하도록 선언하였다. 또한 이러한 상수값을 전역적으로 사용할 때, 더블과 더블간의 연관성이 생기는 경우 제어하기가 쉽지 않다.
그런 경우에는 인스턴스 수준에서 제어할 수 있는 상수변수를 할당하여 사용하였다.
[참고] 더블과 더블간의 연관성이 생기는 케이스
A 객체가 B, C를 의존하고 B의 결과가 C의 인자값으로 들어가는 구조라면 C를 제어하기 위해 B도 제어해야한다.
이런 경우 전역 상수로 둘다 제어하는 것이 쉽지 않고, 제어하더라도 B와 C 코드를 둘다 파악해야하므로 좋지 못하다.
따라서 이런 경우는 인스턴스 변수를 별도로 두어 해당 더블만 제어할 수 있는 변수를 따로 두었다.
구조 중복은 테스트 메서드의 내용이 동일하게 나타나는 경우다.
해당 케이스는 하나의 메서드에 중복된 코드를 몰아넣고 해당 메서드를 호출하는 방식으로 제거할 수 있다.(너무나도 당연한)
또는 컬렉션에 담거나 파라미터화하여 제거할 수 있다.
다행히 구조 중복 케이스는 소나큐브에서 이슈를 잘 띄워줘서 평소에 제거해놓을 수 있었다.
다만, 갑자기 의문이 드는 부분이 생겼다.
DB 데이터와 엔티티 프로퍼티를 변환하는 테스트 코드가 구조가 모두 동일했다.
이런 경우에도 중복을 제거해야하나??
나의 enum class는 아래와 같이 dbData 값과 매핑 되는 enum 타입을 enum 상수에 모두 정의하고 있어,
컨버터 구조도 모두 동일하였다.
enum class ContentsSvcPosbSttsType(val dbData: Int, val desc: String) {
NotRelease(0, "미출시"),
Release(1, "출시"),
Approved(2, "검수 완료"),
Error(3, "오류"),
}
@Converter(autoApply = true)
class ContentsSvcPosbSttsTypeConverter : AttributeConverter<ContentsSvcPosbSttsType, Int> {
override fun convertToEntityAttribute(column: Int?): ContentsSvcPosbSttsType? {
return ContentsSvcPosbSttsType.entries.find { it.dbData == column }
}
override fun convertToDatabaseColumn(property: ContentsSvcPosbSttsType): Int {
return property.dbData
}
}
따라서 테스트 코드도 모두 아래와 같은 구조로 동일 하였다.
@Test
fun `dbData to DocsStatusType`() {
// when
DocsStatusType.entries.forEach { docsStatusType ->
val type = docsErrStatusTypeConverter.convertToEntityAttribute(docsStatusType.dbData)
// then
assertThat(type).isEqualTo(docsStatusType)
}
}
@Test
fun `dbData to ContentsSvcPosbSttsType`() {
// when
ContentsSvcPosbSttsType.entries.forEach { contentsSvcPosbSttsType ->
val type = contentsSvcPosbSttsTypeConverter.convertToEntityAttribute(contentsSvcPosbSttsType.dbData)
// then
assertThat(type).isEqualTo(contentsSvcPosbSttsType)
}
}
일단 테스트 대상이 다른데 서로 다른 테스트 대상을 컬렉션에 담아서 한번에 처리하는 것은 옳지 않다고 생각한다.
컬렉션에 서로 다른 테스트 대상을 담아 하나의 클래스에서 테스트하는건 테스트 독립성을 깨는 것이기에 해당 방법은 배제하였다.
대신, 저 구조를 담은 하나의 유틸 메서드로 만들어 해당 메서드를 통해 모두 검증 되도록 하였다.
아래와 같이 같은 구조를 담은 유틸 메서드를 만들고
object CustomAssertUtil {
// assert db data to enum class type
fun <T : Enum<T>> assertDataToEnumMapping(enumClass: Class<T>, action: (T) -> T?) {
enumClass.enumConstants.forEach { dbData ->
// when
val type = action(dbData)
// then
assertThat(type).isEqualTo(dbData)
}
}
}
호출부에서 해당 메서드를 호출 하는 방식으로 테스트를 진행하였다.
@Test
fun `dbData to ContentsSvcPosbSttsType`() {
assertDataToEnumMapping(ContentsSvcPosbSttsType::class.java) { enum ->
contentsSvcPosbSttsTypeConverter.convertToEntityAttribute(enum.dbData)
}
}
모든 컨버터 테스트에서 유틸 메서드에 인자값만 넣어주는 방식으로 테스트하여 구조 중복을 제거하였다.
2.2 양치기 테스트
양치기 테스트는 간헐적으로 실패하는 테스트이다.
OS에 따라 실핼 결과가 다른 코드가 대표적이다.(ex. timestamp)
나의 테스트 코드에도 양치기 테스트가 존재했다.
S3에 파일을 업로드하는 코드인데, 등록되지 않은 IP나 도메인의 경우 업로드가 실패한다.
해당 케이스는 테스트 더블로 대체하며 해결 하였는데, 테스트 성능 개선을 위해 이전에 반영을 했던 내용이다.
2.3 절대 경로 제거, 상대 경로 사용
위의 양치기 테스트와도 연관성이 높은 부분이다.
OS가 달라지면 루트 경로 형식이 다르기에 절대경로를 사용하게 되면 테스트가 실패할 것이다.
따라서 상대 경로를 사용하자는 것이다.
이미 다 적용되어있는 내용이라 바로 넘어가겠다.
2.4 메서드간 응집력
과거 클린코드라는 책에서 메서드의 응집력을 높이는 코드 패턴을 읽을 적이 있다.
해당 내용은 A메서드가 속성 a,b,c를 사용하고 B메서드가 속성 d,e,f를 사용한다면 A 메서드와 B 메서드는 연관성이 없으므로 분리하는 것을 고려해야한다는 것이다.
여기서도 마찬가지이다. 테스트 코드 작성시 테스트 코드 작성을 위해 필요로한 자원(상수, 속성, 메서드)들이 있을 것이다.
이를 테스트 픽스쳐라고 하는데 테스트 픽스쳐가 다르다면 테스트 코드를 분리하라는 것이다.
이부분은 나의 주관에 따라 기준을 정하였다.
예를 들면 스프링 컨텍스트를 띄워야하는 테스트와 순수 코드로 작성 가능한 테스트는 테스트 대상이 같아도 클래스를 분리하였다.
테스트 메서드 내에서 테스트 대상과 테스트 더블이 서로 같은 인스턴스를 사용하는지까지 구분하는 기준은 세우지 않았다.
이것 까지 구분하면 오히려 응집력을 떨어트릴 수 있다고 판단해 이부분은 분리하지 않았다.
3. 신뢰성
3.1 주석
아무 이유 없이 주석 처리하는 tc라면 차라리 제거하는게 낫다는 내용과 주석 대신 적절한 변수명을 사용하자는 내용이다.
내 tc에는 주석 처리된 tc가 하나 존재하였다.
E2E 테스트 코드였는데, 나는 단위 테스트만 진행하고 통합 테스트나 E2E 테스트 코드는 작성하지 않았다.
혹시 나중에 사용할지도 몰라 e2e 테스트 환경 설정을 하며 하나 작성하였는데,
이후 prod 프로젝트 구조 변경사항과 함께 변경하지 않아 예외가 발생하여 주석 처리를 하였었다.
그 이후 시간이 꽤 지나도록 관리가 되지 않았어서 사용목적이 있을 때 다시 진행하고자 과감하게 제거하기로 하였다.
적절한 변수명을 사용하자는 내용은 주석과 관계 없이 가독성을 위해 필요한 부분이라고 생각한다.
그리고 내 개인적인 생각은 우리나라는 영어권 국가가 아니기 때문에 주석에 대해 좀더 관대하게 생각해도 좋다고 생각한다.
한글로된 주석이 잘 짜여진 영어 메서드, 변수명보다 읽기 편하므로 주석 관리를 잘 한다는 전제하에 주석 사용에 조금 관대하려고 한다.
3.2 낮아진 기대치
테스트 코드가 너무 쉽게 성공하여 제대로된 검증이 불가한 경우이다.
책에서 소개한 예시는 계산식을 검증하는 코드가 동치 검증을 할 수 있음에도 0보다 크다 작다와 같은 비교 계산을 통해 쉽게 성공하고 실패케이스를 제대로 분류하지 못하는 경우이다.
이 내용을 읽으니 가장 먼저 생각났던 코드가 select 쿼리 테스트이다.
select 쿼리 테스트는 Object Relation Mapping이 목적인 테스트이다.
DB data가 클래스에 선언된 타입으로 정상 변환되는지 테스트하는 것이다.
내가 사용하는 querydsl은 클래스 타입이 다르거나 제약조건이 다르면 예외가 발생된다.
따라서 예외만 발생되지 않으면 정상 수행됬다고 판단했다.
단언문을 주로 assertThat(object).isNotNull 이나 assertThat(objectList.size).isNotEmpty와 같은 단순히 조회만 정상적으로 됬는지 테스트 했다.
이러한 테스트가 낮아진 기대치라고 판단했고, 적어도 select 쿼리에서 검색조건으로 사용한 컬럼 조건대로 가져왔는지까지는 테스트 할 수 있다고 생각하여 아래와 같이 테스트를 변경하였다.
private val inHouseDummyEntity = getInHouseContentsDummyEntity()
@Test
fun `문제만 조회`() {
// given
val contentsId = inHouseDummyEntity.contentsId
val memberId = inHouseDummyEntity.memberId
// when
val contents =
mathContentsReadRepository.readContentsOnly(contentsId, memberId)
// then
assertThat(contents!!.contentsId).isEqualTo(contentsId)
assertThat(contents.memberId).isEqualTo(memberId)
}
flyway를 통해 독립적인 테스트 DB환경을 구축하는데,
테스트에서 해당 데이터를 가져오기 위해 flyway로 집어넣은 데이터를 아래와 같이 프로그래밍단에서 사용할 수 있게 더미 데이터 정보를 표현하였다. 엔티티의 모든 칼럼을 다 표현하기에는 내용이 너무 많아 검색조건이나 조인 칼럼으로 사용되는 칼럼 정보만 담아 사용한다.
따라서 해당 정보를 통해 쿼리 조건에 해당하는 데이터가 잘 조회되는지 여부에 대한 테스트로 업그레이드 하였다.
object MathContentsDummyFactory {
// 자체 제작 수학문제
fun getInHouseContentsDummyEntity() =
ExistEntityInfo(
1,
UUID.fromString("10ED5466-CDA8-EA4D-9BC7-037CB86FDB20"),
22003,
1,
2,
ContentsClassifyType.InHouse,
0
)
class ExistEntityInfo(
val contentsId: Long,
val memberId: UUID,
val unitId: Int,
val typeId: Int,
val quesLevel: Int,
val contentsClassifyType: ContentsClassifyType,
val transConCtn: Int
)
}
4. 설계
4.1 SOLID
SOLID 원칙은 테스트 코드 작성을 쉽게 한다.
단일 책임 원칙은 높은 응집력으로 코드 파악을 쉽게하고 테스트 대상과 테스트 대상이 사용하는 자원을 명확하게하여 테스트 코드 작성을 용이하게 만드는 장점이 있다.
개방 폐쇄 원칙은 코드 변경 없이 기능을 확장할 수 있는 구조로 만드는 것처럼 의존객체를 테스트 더블로 쉽게 교체할 수 있도록 해준다.
리스코프 치환원칙은 테스트 코드 작성을 용이하게 만드는데는 크게 도움이 되지는 않지만 계약 테스트를 가능하게 한다.
인터페이스가 정의한 기능을 구현체가 제대로 제공하고 있는지에 대한 검증을 할 수 있다.
인터페이스 분리 원칙은 인터페이스를 작게 분리하여 설계하자는 것으로 이또한 테스트 더블을 쉽게 만들수 있도록 한다.
의존 역전 원칙은 추상화에 의존하자는 개념으로 테스트 더블을 런타임에 쉽게 변경할 수 있는 장점이 있다.
4.2 private, static 메서드 제거
private 메서드를 쉽게 테스트할 수 있는 방법은 없다. 애초에 private 메서드는 테스트할 필요가 없게 만들어져야한다.
private 메서드를 테스트하지 말라는 얘기가 아니다.
만약 private 메서드에 대한 테스트가 필요하다는 생각이 든다면 별도의 클래스에 public으로 구현하여 사용하는 것이 바람직하다.
static메서드는 테스트 더블로 대체하기가 어렵기에 테스트시 대체될 필요가 있다면 static 보다는 인스턴스 메서드로 제공하여 인스턴스를 통해 사용하는 것이 좋다.
private 메서드를 제거하는 것에 대해서는 고민을 좀 해봐야할 듯 하다.
현재 tc에서 private 메서드의 동작을 검증하는 테스트 코드가 존재하기는 한다.
클래스를 분리하게 되면 응집도가 떨어지는 것 같아 그대로 두었는데, 이런 부분은 좀 더 고민을 하고 적용 여부를 고려해봐야겠다.
static은 이전에 제거하였다.
파일 io 유틸 메서드를 static으로 사용하였는데, 해당 메서드를 사용하는 곳에서 모두 파일 IO를 진행하게 되니 좋지 못하여 인스턴스 메서드를 사용하도록 하여 테스트 더블로 대체되도록 하였다.
4.3 new는 대체 불가
4.4 생성자에 로직 구현 제거
4.5 싱글톤 패턴 제거
new 연산자로 사용하는 의존객체를 직접 생성하는 경우에는 테스트 더블로 대체하기 어려워 사용을 지양하자는 것이다.
상속관계에서 생성자는 늘 상위 클래스 생성자까지 실행하므로 상위 클래스 생성자에 로직이 있는 경우 대체하기 어렵다.
테스트 더블로 대체할 필요가 있다면 생성자에 로직은 제거하자.
싱글톤 패턴은 static 메서드를 사용하기에 테스트 더블로 대체하기 어렵다.
위 부분은 내 프로젝트에서는 사용되는 케이스가 없어 바로 넘어가겠다.
4.5 상속보다 합성을
상속 구조는 컴파일 타임에 의존성이 결정되어 이후에 변경할 수 없다.
즉, 테스트 더블로 대체하기 어려운 구조를 갖춘다는 것이다.
이와 반면에 합성은 런타임에 객체를 자유롭게 바꿀 수 있다.
상속을 코드를 묶어버리기에 테스트 대상의 상위 클래스에 정의된 모든 속성과 행위에 대해 테스트를 함께 진행해야하므로 테스트 작성에 어려움을 만들지만, 합성은 캡슐화도 잘되어있고 테스트 더블로 쉽게 대체할 수 있는 구조이기에 테스트 대상에 집중하여 테스트 작성을 쉽게 만든다.
4.6 외부 라이브러리를 감싸라
외부 라이브러리의 경우 제어가 어렵기에 그 사용 범위를 줄일 필요가 있다.
나의 프로젝트는 멀티모듈 구조로 외부 라이브러리에 집중한 기능은 완전히 독립적인 모듈로 구성하였다.
호출하는 곳에서는 인터페이스에만 의존하고 구현체에서 라이브러리 기능을 사용하도록 처리하였다.
이렇게 함으로써 호출자는 라이브러리에 전혀 의존하지 않고 코드를 작성하므로 테스트 코드 작성시에도 라이브러리와 상관 없이 코드를 작성할 수 있다.
후기
과거 나는 테스트 코드를 빠르게 작성하는 것이 중요하다고 생각하여 품질에는 크게 신경 쓰지 않았다.
허나, 테스트 코드는 prod 코드 검증 뿐만 아니라 prod 코드를 설명하는 문서의 역할도 하기에 충분한 노력을 기울이고 작성해야한다.
테스트 코드는 주석이나 문서파일보다 관리가 잘되는 문서이다.
주석이나 문서파일은 시간이 지나면서 최신 상태를 유지하기 어렵지만 테스트 코드는 변경사항과 함께 지속적으로 유지보수가 이루어진다.
따라서 우리는 테스트 코드 관리에도 많은 노력을 해야한다.
성공과 실패를 구분하는 것만이 테스트 코드의 목적이 아니다.
prod 코드를 설명할 수 있어야하며 발생할 수 있는 각종 시나리오 상황을 테스트 코드로 작성해야한다.
이처럼 테스트 코드가 가진 가치가 워낙 크기에 커버리지 100%라는 목표를 세웠고,
테스트 코드에 집중하고 다른 제약조건을 없애기 위한 테스트 환경 구축 및 성능 개선을 하였다.
또한 문서로서의 가치를 확대하고자 가독성에 대한 리팩토링도 진행하였다.
테스트 코드가 모든 것을 완벽하게 검증할 수는 없겠지만, 잘 작성된 테스트 코드는 99%를 걸러낼 수 있다고 생각한다.
우리는 이 1%라는 수치를 최대한 줄이기 위해 테스트 코드 작성과 관리에 노력을 해야하지 않나 생각이 든다.
'테스트' 카테고리의 다른 글
Flyway, TestContainer를 통한 독립된 테스트 DB 환경 구성 (0) | 2025.02.22 |
---|---|
테스트 더블을 직접 구현하여 테스트 환경 구축하기(feat. 동시성 제어) (0) | 2025.02.21 |
스프링 컨텍스트 캐싱을 위한 테스트 환경 구축 (0) | 2025.02.20 |
테스트 커버리지 100% 달성기[2] - 테스트 시간 단축 (0) | 2025.01.27 |
테스트 커버리지 100% 달성기[1] - 레이어별 100% 달성 과정 (2) | 2025.01.04 |