✅ ユニットテスト
📖 ユニットテストとは?
ユニットテストは、コードの小さな単位(関数、クラス)が正しく動作するかを確認するテストです。バグを素早く見つけ、安全にリファクタリングできます!
💡 基本設定
build.gradle.kts
dependencies {
testImplementation("org.jetbrains.kotlin:kotlin-test")
testImplementation("org.junit.jupiter:junit-jupiter:5.9.0")
}
tasks.test {
useJUnitPlatform()
}
🎯 最初のテスト
簡単な関数テスト
// src/main/kotlin/Calculator.kt
class Calculator {
fun add(a: Int, b: Int): Int {
return a + b
}
fun subtract(a: Int, b: Int): Int {
return a - b
}
}
// src/test/kotlin/CalculatorTest.kt
import org.junit.jupiter.api.Test
import kotlin.test.assertEquals
class CalculatorTest {
@Test
fun `덧셈 테스트`() {
val calculator = Calculator()
val result = calculator.add(2, 3)
assertEquals(5, result)
}
@Test
fun `뺄셈 테스트`() {
val calculator = Calculator()
val result = calculator.subtract(5, 3)
assertEquals(2, result)
}
}
🔧 テスト構造
Given-When-Then
class ShoppingCart {
private val items = mutableListOf<String>()
fun addItem(item: String) {
items.add(item)
}
fun getItemCount(): Int = items.size
}
class ShoppingCartTest {
@Test
fun `아이템 을 추가하면 개수가 증가한다`() {
// Given (준비)
val cart = ShoppingCart()
// When (실행)
cart.addItem("사과")
cart.addItem("바나나")
// Then (검증)
assertEquals(2, cart.getItemCount())
}
}
SetupとTeardown
import org.junit.jupiter.api.*
class UserServiceTest {
private lateinit var userService: UserService
@BeforeEach
fun setup() {
// 각 테스트 전에 실행
userService = UserService()
println("테스트 시작")
}
@AfterEach
fun teardown() {
// 각 테스트 후에 실행
println("테스트 끝")
}
@Test
fun `사용자 생성 테스트`() {
val user = userService.createUser("홍길동")
assertEquals("홍길동", user.name)
}
@Test
fun `사용자 조회 테스트`() {
userService.createUser("김철수")
val user = userService.findUser("김철수")
assertNotNull(user)
}
}
class UserService {
private val users = mutableMapOf<String, User>()
fun createUser(name: String): User {
val user = User(name)
users[name] = user
return user
}
fun findUser(name: String): User? = users[name]
}
data class User(val name: String)
🎨 様々なアサーシ ョン
基本的な検証
import kotlin.test.*
class AssertionTest {
@Test
fun `다양한 assertion`() {
// 동등성
assertEquals(5, 2 + 3)
assertNotEquals(4, 2 + 3)
// null 검사
val value: String? = "hello"
assertNotNull(value)
val nullValue: String? = null
assertNull(nullValue)
// 참/거짓
assertTrue(5 > 3)
assertFalse(5 < 3)
// 컬렉션
val list = listOf(1, 2, 3)
assertTrue(list.contains(2))
assertEquals(3, list.size)
}
}
例外テスト
class ValidationTest {
@Test
fun `음수 입력시 예외 발생`() {
val exception = assertFailsWith<IllegalArgumentException> {
validateAge(-1)
}
assertEquals("나이는 0 이상이어야 합니다", exception.message)
}
@Test
fun `유효한 입력은 통과`() {
assertDoesNotThrow {
validateAge(25)
}
}
}
fun validateAge(age: Int) {
require(age >= 0) { "나이는 0 이상이어야 합니다" }
}
inline fun <reified T : Throwable> assertFailsWith(block: () -> Unit): T {
try {
block()
throw AssertionError("예외가 발생하지 않았습니다")
} catch (e: Throwable) {
if (e is T) return e
throw e
}
}
inline fun assertDoesNotThrow(block: () -> Unit) {
block()
}
🔥 実践例
文字列ユーティリティテスト
object StringUtils {
fun reverse(text: String): String {
return text.reversed()
}
fun isPalindrome(text: String): Boolean {
val clean = text.lowercase().filter { it.isLetterOrDigit() }
return clean == clean.reversed()
}
}
class StringUtilsTest {
@Test
fun `문자열 뒤집기`() {
assertEquals("olleh", StringUtils.reverse("hello"))
assertEquals("", StringUtils.reverse(""))
}
@Test
fun `회문 판별`() {
assertTrue(StringUtils.isPalindrome("A man a plan a canal Panama"))
assertTrue(StringUtils.isPalindrome("race car"))
assertFalse(StringUtils.isPalindrome("hello"))
}
}
リスト処理テスト
class ListProcessor {
fun filterEven(numbers: List<Int>): List<Int> {
return numbers.filter { it % 2 == 0 }
}
fun sum(numbers: List<Int>): Int {
return numbers.sum()
}
}
class ListProcessorTest {
private val processor = ListProcessor()
@Test
fun `짝수 필터링`() {
val numbers = listOf(1, 2, 3, 4, 5, 6)
val result = processor.filterEven(numbers)
assertEquals(listOf(2, 4, 6), result)
}
@Test
fun `빈 리스트 처리`() {
val result = processor.filterEven(emptyList())
assertTrue(result.isEmpty())
}
@Test
fun `합계 계산`() {
val numbers = listOf(1, 2, 3, 4, 5)
assertEquals(15, processor.sum(numbers))
}
}
ビジネスロジックテスト
data class Order(val items: List<Item>, val discount: Double = 0.0) {
fun calculateTotal(): Double {
val subtotal = items.sumOf { it.price * it.quantity }
return subtotal * (1 - discount)
}
}
data class Item(val name: String, val price: Double, val quantity: Int)
class OrderTest {
@Test
fun `주문 금액 계산`() {
val order = Order(
items = listOf(
Item("사과", 1000.0, 2),
Item("바나나", 1500.0, 1)
)
)
assertEquals(3500.0, order.calculateTotal())
}
@Test
fun `할인 적용`() {
val order = Order(
items = listOf(Item("상품", 10000.0, 1)),
discount = 0.1 // 10% 할인
)
assertEquals(9000.0, order.calculateTotal())
}
@Test
fun `빈 주문`() {
val order = Order(emptyList())
assertEquals(0.0, order.calculateTotal())
}
}
🎯 パラメータ化テスト
複数のケースを一度に
import org.junit.jupiter.params.ParameterizedTest
import org.junit.jupiter.params.provider.CsvSource
class ParameterizedTests {
@ParameterizedTest
@CsvSource(
"1, 1, 2",
"2, 3, 5",
"10, 20, 30"
)
fun `덧셈 테스트`(a: Int, b: Int, expected: Int) {
val calculator = Calculator()
assertEquals(expected, calculator.add(a, b))
}
}
🛠️ テストのコツ
意味のあるテスト名
class GoodTestNames {
// ❌ 나쁜 예
@Test
fun test1() { }
// ✅ 좋은 예
@Test
fun `이메일 형식이 올바르지 않으면 예외가 발생한다`() { }
@Test
fun `장바구니에 아이템 추가시 개수가 증가한다`() { }
}
独立したテスト
// ❌ 나쁜 예 - 테스트 순서에 의존
class BadTest {
companion object {
var counter = 0
}
@Test
fun testA() {
counter++
assertEquals(1, counter)
}
@Test
fun testB() {
// testA가 먼저 실행되어야 통과
assertEquals(2, ++counter)
}
}
// ✅ 좋은 예 - 독립적
class GoodTest {
@Test
fun testA() {
val counter = 0
assertEquals(1, counter + 1)
}
@Test
fun testB() {
val counter = 1
assertEquals(2, counter + 1)
}
}
🤔 よくある質問
Q1. すべての関数をテストする必要がありますか?
A: 重要なビジネスロジックから始めてください!
// ✅ 테스트 필요
fun calculateDiscount(price: Double): Double
// ❌ 테스트 불필요 (단순 getter)
val name: String get() = _name
Q2. private関数はどうテストしますか?
A: 通常、public関数を通じて間接的にテストします!
class Processor {
fun process(data: String): String {
return clean(data).uppercase() // private 함수 간접 테스트
}
private fun clean(text: String): String {
return text.trim()
}
}
Q3. テストコードが長すぎる場合は?
A: ヘルパー関数を作成して再利用してください!
class TestHelpers {
fun createTestUser(name: String = "테스트") = User(name)
fun createTestOrder(itemCount: Int = 1) = Order(
items = List(itemCount) { Item("상품$it", 1000.0, 1) }
)
}
🎬 まとめ
ユニットテストで安定したコードを!
핵심 정리:
✅ @Test로 테스트 작성
✅ assertEquals로 검증
✅ Given-When-Then 패턴
✅ 독립적인 테스트 유지
✅ 의미있는 테스트 이름
次のステップ: Assertionでより強力な検証方法を学びましょう!