티스토리 뷰
코프링으로 개발하면서 리팩터링 내성 관련해서 공부한 내용을 포스팅해봤습니다.
개발 환경은 다음과 같습니다.
- Spring Boot Version : 3.0.1
- Java Version : 17
https://github.com/laboratory-kkoon9/kotlin-spring
배경
비밀번호 암호화를 하는 과정에서 다음과 같은 테스트 코드를 작성했습니다.
package com.laboratorykkoon9.kotlinspring.common
import io.kotest.core.spec.style.BehaviorSpec
import io.kotest.matchers.shouldBe
import io.kotest.matchers.shouldNotBe
internal class ExtensionsTest: BehaviorSpec({
val key = "keyskeyskeyskeys"
given("String.encrypt") {
val password = "hello"
`when`("hello를 암호화하면") {
val result = password.encrypt(key)
then("hello가 아닌 다른 값이 반환된다.") {
result shouldNotBe password
}
}
}
given("String.decrypt") {
val password = "hello"
`when`("암호화한 hello를 복호화하면") {
val encrypt = password.encrypt(key)
val result = encrypt.decrypt(key)
then("hello 값이 반환된다.") {
result shouldBe password
}
}
}
})
위 코드처럼 먼저 암호화하는 확장 함수를 만들고 그에 대한 테스트 코드를 짰습니다.
그 다음, User 객체를 생성할 때 password를 암호화하는 도메인 로직을 짠 뒤, 그에 대한 테스트 코드도 작성했습니다.
도메인 로직 코드
package com.laboratorykkoon9.kotlinspring.auth.domain
import com.laboratorykkoon9.kotlinspring.common.BaseEntity
import com.laboratorykkoon9.kotlinspring.common.encrypt
import jakarta.persistence.*
@Entity
@Table(name = "users")
class User(
nickname: String,
email: String,
password: String,
key: String,
) : BaseEntity() {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
val id: Long? = null
var nickname: String = nickname
private set
var email: String = email
private set
var password: String = password.encrypt(key).toString()
private set
}
테스트 코드
package com.laboratorykkoon9.kotlinspring.auth.domain
import io.kotest.core.spec.style.BehaviorSpec
import io.kotest.matchers.shouldNotBe
internal class UserTest : BehaviorSpec({
given("User") {
val nickname = "겸댕"
val email = "cute@gmail.com"
val password = "password123!"
val key = "keyskeyskeyskeys"
`when`("새로운 User 인스턴스를 만들면") {
val user = User(nickname, email, password, key)
then("password 값은 암호화되어 저장된다.") {
user.password shouldNotBe password
}
}
}
})
리팩터링 내성
문득 책 Unit Testing에서 리팩터링 내성을 배운게 생각났습니다.
리팩터링 내성(refactoring resilience)은 코드 리팩터링 후에도 테스트가 여전히 올바른 동작을 보장하는 능력을 의미합니다.
코드의 변경이나 개선이 있을 때에도 기존의 테스트가 실패하지 않고 계속 통과하는 것이 중요합니다.
리팩터링 내성을 판단하기 위해서는 코드 변경 여부와 관련된 몇 가지 요소를 고려해야 합니다.
1. 테스트 명료성
테스트 케이스의 이름과 설명이 명확한지 확인합니다.
테스트의 의도를 알기 쉽게 표현하는 것이 리팩터링 내성을 높이는데 도움이 됩니다.
2. 테스트 커버리지
코드의 변경에도 불구하고 테스트 케이스가 여전히 중요한 코드 경로를 커버하고 있는지 확인합니다.
테스트 커버리지가 충분하지 않으면 코드 리팩터링 후에 버그가 발생할 가능성이 높아집니다.
3. 의존성 분리
테스트에서 사용되는 데이터나 외부 의존성이 변경 사항에 민감하지 않도록 분리되어 있는지 확인합니다.
예를 들어, 외부 시스템과의 통합을 테스트하는 경우, 이 통합 부분을 모의(mock)나 스텁(stub)으로 대체하여 의존성을 분리할 수 있습니다.
4. 테스트 케이스 구조
테스트 케이스의 구조가 모듈화되어 있고, 변경된 부분만 수정해서 리팩터링할 수 있는지 확인합니다.
중복된 코드를 최소화하고 재사용 가능한 테스트 유틸리티를 사용하는 것도 고려하는 게 좋습니다.
5. 선언적 어설션
테스트에서 사용되는 어설션(Assertion)이 선언적으로 작성되어 코드 변경에 민감하지 않도록 하는 것이 좋습니다.
어설션은 테스트의 예상 동작을 명확하게 표현해야 합니다.
6. 가독성과 유지보수성
테스트 코드가 가독성 있고 유지보수 가능한지 확인합니다.
코드 리팩터링은 코드의 가독성을 향상시키는데 도움이 되어야 합니다.
나의 견해
저는 이러한 리팩터링 내성 판단 근거 중 User Class Test Code는 코드의 변경에 취약할 수 있겠다고 생각했습니다.
예시1) 암호화 방식을 변경하여 key에 대한 값이 필요없어지는 경우
예시2) User Class의 필드가 새로 추가되는 경우
저는 비밀번호가 암호화됐는지 테스트하는 케이스가 필드 추가로 인해 실패 혹은 컴파일 오류가 발생하는 것 자체로 리팩터링 내성이 부족하다고 느꼈습니다.
결론
결론은 UserTest 내 password가 암호화가 잘 됐는지에 대한 테스트 케이스는 맨 위에서 제시한 ExtensionTest에 맡기고, 해당 케이스는 삭제하였습니다.
이에 대한 내용은 계속 공부 중에 있어서 혹여나 제 견해가 바뀌게 되면 다른 포스팅으로 찾아뵙겠습니다.
읽어주셔서 감사합니다.
'개발 노트' 카테고리의 다른 글
[스프링] yaml 파일 하나로 스프링 환경변수 관리하기 (0) | 2023.09.01 |
---|---|
[스프링+자바] Failed to deserialize java.time.LocalDateTime (0) | 2023.08.28 |
git merge 옵션 squash (0) | 2023.08.10 |
intellij에서 git 커밋 순서 바꾸기 (0) | 2023.08.02 |
[스프링+코틀린] springfox @ApiModelProperty "is"가 붙은 변수 미노출 버그 (0) | 2023.07.25 |
- Total
- Today
- Yesterday
- Spring
- 클린 아키텍처
- MSA
- 알고리즘
- Java
- JPA
- AWS
- 테라폼
- 객체지향
- 정규표현식
- 이팩티브 자바
- 이펙티브 자바
- Algorithm
- Spring Boot
- Olympiad
- node.js
- 클린 코드
- 백준
- kotest
- Kotlin
- 프로그래머스
- BAEKJOON
- C++
- kkoon9
- programmers
- 코테
- 디자인패턴
- Effective Java
- BOJ
- 디자인 패턴
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |