Saltar al contenido principal

✅ Pruebas Unitarias

📖 ¿Qué son las Pruebas Unitarias?

Las Pruebas Unitarias son pruebas que verifican si pequeñas unidades de código (funciones, clases) funcionan correctamente. Le permiten encontrar errores rápidamente y refactorizar de manera segura!

💡 Configuración Básica

build.gradle.kts

dependencies {
testImplementation("org.jetbrains.kotlin:kotlin-test")
testImplementation("org.junit.jupiter:junit-jupiter:5.9.0")
}

tasks.test {
useJUnitPlatform()
}

🎯 Su Primera Prueba

Prueba de Función 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)
}
}

🔧 Estructura de Pruebas

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 y 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)

🎨 Diversas Aserciones

Verificaciones Básicas

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)
}
}

Pruebas de Excepciones

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()
}

🔥 Ejemplos Prácticos

Prueba de Utilidad de Cadenas

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"))
}
}

Prueba de Procesamiento de Listas

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))
}
}

Prueba de Lógica de Negocio

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())
}
}

🎯 Pruebas Parametrizadas

Múltiples Casos a la Vez

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))
}
}

🛠️ Consejos para Pruebas

Nombres de Prueba Significativos

class GoodTestNames {
// ❌ 나쁜 예
@Test
fun test1() { }

// ✅ 좋은 예
@Test
fun `이메일 형식이 올바르지 않으면 예외가 발생한다`() { }

@Test
fun `장바구니에 아이템 추가시 개수가 증가한다`() { }
}

Pruebas Independientes

// ❌ 나쁜 예 - 테스트 순서에 의존
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)
}
}

🤔 Preguntas Frecuentes

P1. ¿Debo probar todas las funciones?

R: ¡Comience con la lógica de negocio importante!

// ✅ 테스트 필요
fun calculateDiscount(price: Double): Double

// ❌ 테스트 불필요 (단순 getter)
val name: String get() = _name

P2. ¿Cómo pruebo las funciones privadas?

R: ¡Generalmente se prueban indirectamente a través de funciones públicas!

class Processor {
fun process(data: String): String {
return clean(data).uppercase() // private 함수 간접 테스트
}

private fun clean(text: String): String {
return text.trim()
}
}

P3. ¿Qué hacer si el código de prueba es demasiado largo?

R: ¡Cree funciones auxiliares para reutilización!

class TestHelpers {
fun createTestUser(name: String = "테스트") = User(name)

fun createTestOrder(itemCount: Int = 1) = Order(
items = List(itemCount) { Item("상품$it", 1000.0, 1) }
)
}

🎬 Conclusión

¡Código estable a través de pruebas unitarias!

핵심 정리:
✅ @Test로 테스트 작성
✅ assertEquals로 검증
✅ Given-When-Then 패턴
✅ 독립적인 테스트 유지
✅ 의미있는 테스트 이름

Siguiente Paso: ¡Aprenda métodos de verificación más potentes en Assertion!