Volver a SDET

Conceptos y Ejercicios de Testing

Looking for JavaScript fundamentals (variables, promises, classes…)? They live in the JavaScript path.Go to JavaScript →
¿Cuáles son los tipos de test doubles?
Un test double es cualquier sustituto de un componente real. Dummy: se pasa pero nunca se usa. Stub: devuelve valores fijos predefinidos. Fake: una implementación funcional pero simplificada (p. ej. una BD en memoria). Spy: registra cómo se le llamó sin cambiar el comportamiento. Mock: un stub más aserciones sobre cómo se le llamó (p. ej. que sendEmail se llamó una vez).
¿Por qué y cuándo se mockea, y cuándo no?
Mockea para aislar la unidad bajo test de dependencias externas lentas o inestables (BD, APIs de terceros, email), para simular errores difíciles de reproducir como un 500, y para mantener los tests rápidos y deterministas. No mockees cuando quieres verificar específicamente la integración real, cuando un mock se alejaría tanto de la realidad que pierde valor, o cuando la dependencia real ya es local y rápida.
¿Cómo se mockean peticiones de red en Playwright?
Usa page.route para interceptar un patrón de URL y resolverlo con una respuesta predefinida: page.route('**/api/products', r => r.fulfill({ status: 200, body: JSON.stringify(data) })). Puedes devolver un 500 para probar la UI de error, o añadir un retardo antes de route.continue() para probar estados de carga — todo sin tocar el backend real.
¿Cómo se estructura un test de integración CRUD para una API REST?
Cubre el ciclo de vida completo y los límites de auth/validación: crear (POST -> 201), leerlo (GET -> 200), actualizar parcialmente (PATCH -> 200, los campos no enviados se mantienen), eliminar (DELETE -> 204) y confirmar que ya no existe (GET -> 404). Añade casos negativos: sin token -> 401, rol incorrecto -> 403, falta un campo obligatorio -> 422, duplicado -> 409. Cada test debe preparar sus propios datos para ser independiente.
¿Qué es el TTL y dónde importa en testing?
El TTL (time to live) es cuánto tiempo algo sigue siendo válido: tokens/sesiones de auth (caducidad del JWT), entradas de cache (Redis, CDN, Cache-Control de HTTP) y ventanas de rate-limit (X-RateLimit-Reset). Como SDET pruebas que un token caducado se rechaza, que una entrada de cache es un miss tras su TTL, y que las peticiones que superan el límite se limitan y se reinician correctamente.
¿Por qué evitar los sleeps fijos y qué se usa en su lugar?
Un sleep fijo (waitForTimeout(3000)) es demasiado corto (flaky) o demasiado largo (lento), porque el timing real varía. Usa esperas basadas en condiciones: waitForSelector para un elemento, expect(...).toBeVisible(), waitForResponse para una llamada de API, o un helper de polling genérico que reverifica una condición hasta un timeout. Esto hace los tests más rápidos y más fiables.
¿Cómo funcionan los reintentos con backoff exponencial y cuándo no reintentar?
El retry reejecuta una operación fallida hasta N intentos, esperando más entre cada intento (delay * 2^intento) para que un servicio en apuros se recupere sin saturarlo. Reintenta solo fallos transitorios (cortes de red, 5xx, timeouts). No reintentes errores de cliente deterministas como un 404 o 422 — un predicado shouldRetry te permite saltarlos, ya que reintentar nunca tendrá éxito.
¿Los entrevistadores preguntan sobre el uso de herramientas de IA y cómo debería responder un candidato?
Sí, en entrevistas técnicas recientes — especialmente para roles mejor remunerados — es habitual que se pregunte si el candidato usa herramientas de IA, cuáles usa y cómo las usa. Los entrevistadores evalúan no solo si se usa la IA, sino si se la usa de forma consciente y con comprensión genuina del resultado. Una respuesta sólida es explicar que se utiliza la IA para acelerar tareas que ya se dominan y que siempre se revisa y comprende el código o las sugerencias generadas. Algunos desafíos take-home ahora incluso requieren un archivo dedicado (por ejemplo, AI_PROCESS.md) que documente cómo se utilizó la IA a lo largo del proyecto.
Tienes un problema en el backend que ocurre de forma intermitente — a veces sucede y otras veces no. ¿Cómo encararias el diagnóstico y la resolución de este problema?
Comienza revisando los logs existentes para identificar patrones en torno a cuándo ocurre el problema. Si no hay logging implementado, agrégalo en las APIs y servicios relevantes para capturar datos de solicitudes y respuestas HTTP, incluyendo códigos de estado (por ejemplo, 200 OK, 500 Server Error, 400 Bad Request). Utiliza herramientas de monitoreo en la nube como AWS CloudWatch si tu infraestructura lo permite. Implementa bloques try/catch para capturar y registrar excepciones en el punto de falla. Luego, delimita el alcance: identifica dónde en el sistema se origina el problema y bajo qué condiciones aparece — como hora del día, carga de tráfico o valores de entrada específicos. Finalmente, considera implementar lógica de reintentos (retry logic) para manejar fallas transitorias de forma adecuada, ya que los problemas intermitentes suelen estar causados por indisponibilidad temporal de red o recursos.
Durante una prueba de selección técnica, te muestran un pseudocódigo de una función que itera sobre un arreglo, acumula la suma de todos los elementos y luego la divide entre la cantidad total. ¿Qué calcula la función y cuál es su complejidad temporal?
La función calcula la media aritmética (promedio) de los elementos del arreglo. Su complejidad temporal es O(n), porque el algoritmo realiza exactamente un recorrido por los n elementos para acumular la suma, seguido de una división en tiempo constante. El número de operaciones crece de forma lineal con el tamaño de la entrada.
¿Cuál es la diferencia entre verificar igualdad de conjuntos y calcular la intersección de conjuntos al comparar dos arreglos?
La igualdad de conjuntos (por ejemplo, set1.equals(set2) en Java) devuelve verdadero solo si ambos conjuntos contienen exactamente los mismos elementos: es una verificación todo o nada. La intersección de conjuntos (por ejemplo, set1.retainAll(set2) en Java o set(a) & set(b) en Python) devuelve el subconjunto de elementos que aparecen en ambas colecciones. Cuando el objetivo es encontrar qué elementos específicos de un arreglo existen en otro, la intersección es la operación correcta. La igualdad solo es apropiada cuando se necesita verificar que los dos arreglos contienen exactamente los mismos valores únicos.
¿Qué es el Test-Driven Development (TDD) y puede aplicarse a una aplicación legacy existente?
TDD es una metodología donde primero se escribe un test fallido, luego se escribe el código mínimo para que pase y finalmente se refactoriza. No se puede aplicar TDD retroactivamente a código ya escrito, pero se puede adoptar la disciplina TDD para todo el trabajo nuevo. Para código legacy, se aplican los principios de TDD añadiendo tests de caracterización antes de refactorizar la lógica existente, creando una red de seguridad sin reescribir todo de una vez.
Escribe una función recursiva para calcular el factorial de un número. ¿Cuál es el caso base y cómo se manejan los casos borde?
Una función factorial recursiva necesita dos partes: 1. **Caso base** (detiene la recursión): cuando n es igual a 0 o 1, retornar 1, ya que 0! = 1! = 1. 2. **Caso recursivo**: retornar n multiplicado por factorial(n - 1). Pseudocódigo / forma general: funcion factorial(n): si (n == 0 || n == 1): retornar 1 sino: retornar n * factorial(n - 1) Puntos clave: - Siempre definir el caso base primero para evitar recursión infinita (desbordamiento de pila). - Agregar una precondición para entradas negativas (por ejemplo, lanzar un error), ya que el factorial no está definido para números negativos. - Complejidad temporal: O(n) — una llamada recursiva por cada decremento. - Cada llamada se apila en el call stack siguiendo el orden LIFO; los resultados se multiplican a medida que la pila se desenrolla.
Dados dos arreglos de enteros, ¿cómo encontrarías los elementos que son comunes a ambos (es decir, qué elementos del primer arreglo también aparecen en el segundo)?
Existen varios enfoques según el lenguaje: **Java** – Convierte ambos arreglos a objetos List y utiliza retainAll(), que modifica la primera lista en lugar para conservar solo los elementos que también existen en la segunda: java import java.util.ArrayList; import java.util.Arrays; import java.util.List; public class Main { public static void main(String[] args) { List<Integer> list1 = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5)); List<Integer> list2 = new ArrayList<>(Arrays.asList(3, 4, 5, 6, 7)); list1.retainAll(list2); System.out.println("Elementos comunes: " + list1); // [3, 4, 5] } } **JavaScript** – Usa filter() combinado con includes(): javascript const array1 = [1, 2, 3, 4, 5]; const array2 = [3, 4, 5, 6, 7]; const comunes = array1.filter(elemento => array2.includes(elemento)); console.log(comunes); // [3, 4, 5] **Python** – Usa la intersección de conjuntos: python def elementos_comunes(a, b): return set(a) & set(b) Nota: La conversión a conjuntos elimina duplicados. Si es necesario preservar los duplicados de los arreglos originales, se debe usar el enfoque basado en filtrado.