Saltar a contenido

AA4 · Ejercicios de inyección de dependencias y pruebas

Como en las unidades anteriores: escribe la solución en un .kt y comprueba que tu salida coincide con el resultado esperado, obtenido ejecutando la solución modelo. En los ejercicios AA4.4 y AA4.6 escribes pruebas y la comprobación es que se ejecuten todas con éxito. Necesitas JUnit 4, kotlin-test y kotlinx-coroutines-test en tu proyecto. Los marcados con ⭐ son más difíciles.

Ejercicio AA4.1

Inyectar el reloj. Haz que Saludo reciba el Reloj por el constructor y devuelva el saludo según la hora, sin leer nunca la hora del sistema.

interface Reloj { fun hora(): Int }

// De 6 a 11 -> "Buenos días"; de 12 a 20 -> "Buenas tardes"; el resto -> "Buenas noches"
class Saludo(/* ... */) {
    fun saludo(): String { /* ... */ return "" }
}

Para comprobarlo:

fun main() {
    var h = 0
    val saludo = Saludo(object : Reloj { override fun hora() = h })
    for (hora in listOf(3, 6, 11, 12, 20, 21)) {
        h = hora
        println("$hora h -> ${saludo.saludo()}")
    }
}

Resultado esperado:

3 h -> Buenas noches
6 h -> Buenos días
11 h -> Buenos días
12 h -> Buenas tardes
20 h -> Buenas tardes
21 h -> Buenas noches
Solución modelo (bloqueada)

🔒 La solución completa está bloqueada. Esto es solo un ejemplo de cómo empieza:

interface Reloj { fun hora(): Int }
class Saludo(private val reloj: Reloj) {
    fun saludo(): String = when (reloj.hora()) {
        in 6..11 -> "Buenos días"
        in 12..20 -> "Buenas tardes"
// ... (resto de la solución bloqueado)

Ejercicio AA4.2

Un contenedor a mano. Escribe Contenedor: un único Servicio compartido que se crea solo cuando se pide por primera vez, y una Pantalla nueva en cada llamada a crearPantalla().

class Servicio {
    init { creados++ }
    companion object { var creados = 0 }
}

class Pantalla(val servicio: Servicio)

// Un solo Servicio compartido, creado SOLO cuando se pide por primera vez.
// crearPantalla() devuelve una Pantalla nueva cada vez que se llama.
class Contenedor {
    // ...
}

Para comprobarlo:

fun main() {
    val c = Contenedor()
    println("servicios creados al principio: ${Servicio.creados}")
    val p1 = c.crearPantalla()
    val p2 = c.crearPantalla()
    val p3 = c.crearPantalla()
    println("servicios creados tras 3 pantallas: ${Servicio.creados}")
    println("¿mismo servicio en todas? ${p1.servicio === p2.servicio && p2.servicio === p3.servicio}")
    println("¿pantallas distintas? ${p1 !== p2 && p2 !== p3}")
}

Resultado esperado:

servicios creados al principio: 0
servicios creados tras 3 pantallas: 1
¿mismo servicio en todas? true
¿pantallas distintas? true
Solución modelo (bloqueada)

🔒 La solución completa está bloqueada. Esto es solo un ejemplo de cómo empieza:

class Servicio {
    init { creados++ }
    companion object { var creados = 0 }
}
class Pantalla(val servicio: Servicio)
// ... (resto de la solución bloqueado)

Ejercicio AA4.3

Esperar sin esperar. Escribe contarHasta y comprueba con runTest que, aunque son 8 segundos de espera, el tiempo transcurrido es virtual.

// Espera 2 segundos entre cada número y devuelve los números de 1 a n.
suspend fun contarHasta(n: Int): List<Int> {
    // ...
}

Para comprobarlo:

fun main() = runTest {
    val lista = contarHasta(4)
    println(lista)
    println("tiempo virtual: ${testScheduler.currentTime} ms")
}

Resultado esperado:

[1, 2, 3, 4]
tiempo virtual: 8000 ms
Solución modelo (bloqueada)

🔒 La solución completa está bloqueada. Esto es solo un ejemplo de cómo empieza:

suspend fun contarHasta(n: Int): List<Int> {
    val lista = mutableListOf<Int>()
    for (i in 1..n) {
        delay(2000)
        lista += i
// ... (resto de la solución bloqueado)

Ejercicio AA4.4

Tus primeras pruebas. Escribe la clase CarritoTest con tres pruebas para este reductor: añadir suma una unidad, quitar no baja de cero y vaciar deja cero unidades.

data class CarritoState(val unidades: Int = 0)

sealed interface EventoCarrito {
    data object Añadir : EventoCarrito
    data object Quitar : EventoCarrito
    data object Vaciar : EventoCarrito
}

fun reducir(estado: CarritoState, evento: EventoCarrito): CarritoState = when (evento) {
    EventoCarrito.Añadir -> estado.copy(unidades = estado.unidades + 1)
    EventoCarrito.Quitar -> estado.copy(unidades = maxOf(0, estado.unidades - 1))
    EventoCarrito.Vaciar -> estado.copy(unidades = 0)
}

Resultado esperado al ejecutar tus pruebas con JUnit:

...
OK (3 tests)
Solución modelo (bloqueada)

🔒 La solución completa está bloqueada. Esto es solo un ejemplo de cómo empieza:

import kotlin.test.*
data class CarritoState(val unidades: Int = 0)
sealed interface EventoCarrito {
    data object Añadir : EventoCarrito
    data object Quitar : EventoCarrito
// ... (resto de la solución bloqueado)

Ejercicio AA4.5

Probar un Flow con tiempo. Escribe temporizador() y comprueba con runTest que emite [1, 2, 3] y que el tiempo virtual es de 3000 ms.

// Emite 1, 2 y 3, esperando un segundo ANTES de cada uno.
fun temporizador(): Flow<Int> {
    // ...
}

Para comprobarlo:

fun main() = runTest {
    val valores = temporizador().toList()
    println(valores)
    println("tiempo virtual: ${testScheduler.currentTime} ms")
}

Resultado esperado:

[1, 2, 3]
tiempo virtual: 3000 ms
Solución modelo (bloqueada)

🔒 La solución completa está bloqueada. Esto es solo un ejemplo de cómo empieza:

fun temporizador(): Flow<Int> = flow {
    for (i in 1..3) {
        delay(1000)
        emit(i)
    }
// ... (resto de la solución bloqueado)

Ejercicio AA4.6

⭐ Probar el debounce. Escribe la clase BuscadorTest con dos pruebas para este Buscador: (1) cinco pulsaciones seguidas, con 50 ms entre ellas, producen una sola búsqueda con el último texto; (2) un texto de una sola letra no lanza ninguna búsqueda.

@OptIn(FlowPreview::class, ExperimentalCoroutinesApi::class)
class Buscador(private val buscar: suspend (String) -> List<String>) {
    fun resultados(teclado: Flow<String>): Flow<List<String>> = teclado
        .debounce(300)
        .distinctUntilChanged()
        .filter { it.length >= 2 }
        .mapLatest { buscar(it) }
}

Resultado esperado al ejecutar tus pruebas con JUnit:

..
OK (2 tests)
Solución modelo (bloqueada)

🔒 La solución completa está bloqueada. Esto es solo un ejemplo de cómo empieza:

import kotlin.test.*
@OptIn(FlowPreview::class, ExperimentalCoroutinesApi::class)
class Buscador(private val buscar: suspend (String) -> List<String>) {
    fun resultados(teclado: Flow<String>): Flow<List<String>> = teclado
        .debounce(300)
// ... (resto de la solución bloqueado)