✅ Tests Unitaires
📖 Qu'est-ce qu'un Test Unitaire?
Les Tests Unitaires sont des tests qui vérifient si de petites unités de code (fonctions, classes) fonctionnent correctement. Ils vous permettent de trouver rapidement des bugs et de refactoriser en toute sécurité!
💡 Configuration de Base
build.gradle.kts
dependencies {
testImplementation("org.jetbrains.kotlin:kotlin-test")
testImplementation("org.junit.jupiter:junit-jupiter:5.9.0")
}
tasks.test {
useJUnitPlatform()
}
🎯 Votre Premier Test
Test de Fonction Simple
// 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)
}
}
🔧 Structure de Test
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 et 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)
🎨 Diverses Assertions
Vérifications de Base
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)
}
}
Tests d'Exceptions
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()
}
🔥 Exemples Pratiques
Test d'Utilitaire de Chaînes
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"))
}
}
Test de Traitement de Listes
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))
}
}
Test de Logique Métier
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())
}
}
🎯 Tests Paramétrés
Plusieurs Cas à la Fois
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))
}
}
🛠️ Conseils de Test
Noms de Test Significatifs
class GoodTestNames {
// ❌ 나쁜 예
@Test
fun test1() { }
// ✅ 좋은 예
@Test
fun `이메일 형식이 올바르지 않으면 예외가 발생한다`() { }
@Test
fun `장바구니에 아이템 추가시 개수가 증가한다`() { }
}
Tests Indépendants
// ❌ 나쁜 예 - 테스트 순서에 의존
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)
}
}
🤔 Questions Fréquentes
Q1. Dois-je tester chaque fonction?
R: Commencez par la logique métier importante!
// ✅ 테스트 필요
fun calculateDiscount(price: Double): Double
// ❌ 테스트 불필요 (단순 getter)
val name: String get() = _name
Q2. Comment tester les fonctions privées?
R: Elles sont généralement testées indirectement via les fonctions publiques!
class Processor {
fun process(data: String): String {
return clean(data).uppercase() // private 함수 간접 테스트
}
private fun clean(text: String): String {
return text.trim()
}
}
Q3. Que faire si le code de test est trop long?
R: Créez des fonctions d'aide pour la réutilisation!
class TestHelpers {
fun createTestUser(name: String = "테스트") = User(name)
fun createTestOrder(itemCount: Int = 1) = Order(
items = List(itemCount) { Item("상품$it", 1000.0, 1) }
)
}
🎬 Conclusion
Code stable grâce aux tests unitaires!
핵심 정리:
✅ @Test로 테스트 작성
✅ assertEquals로 검증
✅ Given-When-Then 패턴
✅ 독립적인 테스트 유지
✅ 의미있는 테스트 이름
Prochaine Étape: Apprenez des méthodes de vérification plus puissantes dans Assertion!