Planteamiento y alcance
La función inicia una goroutine en segundo plano, reintenta con retroceso exponencial y registra un resultado a través de context.AfterFunc cuando ocurre un tiempo de espera. Las pruebas que usan time.Sleep son lentas, inestables o dejan trabajo en segundo plano. Usa Go testing/synctest para diseñar pruebas deterministas y cubrir las API experimentales frente a estables, fugas de goroutines, I/O real y límites de reloj.
La API y la versión deben coincidir con la versión de Go objetivo. Esto se adapta a roles de desarrollo backend, bibliotecas de concurrencia e infraestructura. La habilidad fundamental son las pruebas concurrentes deterministas, por lo que pertenece a coding.
Qué evalúan los entrevistadores
Primero, ¿entiendes qué es una burbuja de synctest? Aísla las goroutines y el tiempo, mientras que Wait permite que el trabajo dentro de la burbuja alcance un estado de reposo (quiescence).
Segundo, ¿puedes separar el tiempo virtual del real? Las API de tiempo utilizadas dentro de una burbuja pueden avanzar virtualmente, pero el comportamiento de la red real, los archivos o las goroutines externas no se vuelve determinista automáticamente.
Tercero, ¿puedes probar la cancelación y la limpieza? La cancelación de contexto, un callback de AfterFunc y las goroutines de reintento deben finalizar o detenerse explícitamente antes de que termine la prueba.
Cuarto, ¿puedes manejar las diferencias de versión? Go 1.24 expuso synctest de forma experimental detrás de un flag; Go 1.25 proporciona la API estable. El CI debe fijar la versión.
Quinto, ¿puedes mantener la cobertura de integración? Las pruebas de tiempo virtual verifican el orden lógico, no el comportamiento de HTTP, la base de datos, el planificador o el detector de carreras en un proceso real.
Preguntas para aclarar primero
- ¿El CI utiliza el experimento de Go 1.24 o la API estable de Go 1.25?
- ¿Todas las goroutines de prueba se crean dentro de la burbuja?
- ¿Los reintentos usan
time.After, un temporizador o un planificador externo? - Tras la cancelación, ¿puede finalizar la I/O actual o debe retornar de inmediato?
- ¿Necesitamos latencia de red real, bloqueos de base de datos y cobertura de carreras?
- ¿Puede el código recibir un reloj o una interfaz de dependencias, o se debe envolver la función existente?
Estructura de respuesta en 30 segundos
“Ejecutaría la función dentro de una burbuja de synctest.Test, avanzaría el tiempo virtual a través de los retrocesos y los plazos límite, y usaría synctest.Wait para alcanzar el estado de reposo. Haría aserciones sobre el número de reintentos, el error final, el comportamiento único de AfterFunc y la ausencia de trabajo tras la cancelación. Go 1.24 requiere el flag experimental; Go 1.25 tiene la API estable, por lo que el CI fija la versión. Las comprobaciones reales de HTTP, bases de datos, planificador y carreras se ejecutan por separado porque no están controladas automáticamente por la burbuja.”
Respuesta paso a paso
Paso 1: Fijar la versión y la API
El paquete testing/synctest de Go 1.24 era experimental y requería GOEXPERIMENT=synctest; Go 1.25 expone la API estándar Test y Wait. Fija go.mod, la imagen de CI y el entorno de línea de comandos para que la semántica local y la de CI coincidan.
Paso 2: Poner el trabajo dentro de la burbuja
Crea cada goroutine de prueba dentro de la burbuja en lugar de iniciar trabajo no controlado fuera de la función de prueba. La burbuja debe ser dueña de la cancelación, el cierre de canales y la limpieza de recursos, y todas las tareas deben retornar antes de que esta termine.
func TestRetryTimeout(t *testing.T) {
synctest.Test(t, func(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
got := runWithRetry(ctx)
synctest.Wait()
// Advance virtual time and assert retry/timeout state here.
_ = got
})
}Paso 3: Avanzar el tiempo virtual
No uses time.Sleep real para el retroceso. Avanza el reloj de la burbuja hasta el siguiente temporizador o plazo límite explícito, luego llama a Wait para que se ejecuten las goroutines listas para correr. Haz aserciones después de cada avance en lugar de omitir múltiples límites, lo que ocultaría la transición que falla.
Paso 4: Verificar AfterFunc y la cancelación
context.AfterFunc inicia una goroutine de callback cuando ocurre la cancelación. Registra el conteo de callbacks y el error observado, luego llama a Wait tras la cancelación. Si la función registra callbacks repetidamente, valida mediante aserciones que la ruta de cancelación genere únicamente los efectos secundarios diseñados.
Paso 5: Cubrir los reintentos y los resultados finales
Inyecta una secuencia de fallos controlada, como dos fallos seguidos de un éxito, y valida cada retroceso y conteo de llamadas. Prueba también el vencimiento del plazo límite, la cancelación del contexto padre y el fallo permanente; la cancelación debe detener los reintentos posteriores y mantener estable la clasificación de errores.
Paso 6: Comprobar los límites de la burbuja
Es posible que las goroutines iniciadas fuera de la burbuja, las llamadas al sistema, la red real y los runtimes de terceros no utilicen el tiempo virtual. Reemplaza esas dependencias con interfaces o pruébalas en suites de integración; el determinismo de la burbuja no es una garantía de extremo a extremo.
Paso 7: Combinar otras herramientas de verificación
Synctest cubre la sincronización lógica. go test -race comprueba carreras de datos, mientras que las pruebas reales con HTTP y bases de datos comprueban conexiones, plazos y comportamiento del protocolo. Monitorea la duración de las pruebas, los límites de reintento y la convergencia de tareas para que las pruebas no sufran fugas silenciosas ni se ralenticen.
Respuesta modelo
“Primero fijaría la versión de Go. El paquete synctest de Go 1.24 necesita un flag experimental; Go 1.25 usa synctest.Test y synctest.Wait estables. Crearía todas las goroutines de prueba dentro de la burbuja, inyectaría una secuencia de fallos, avanzaría el tiempo virtual a través de los retrocesos y el plazo límite, esperaría el estado de reposo tras cada avance y validaría llamadas, errores, callbacks y la limpieza tras la cancelación.
No usaría esperas reales (sleeps) ni trataría las goroutines de red, bases de datos o de terceros fuera de la burbuja como trabajo de tiempo virtual. Esas rutas obtienen fakes de interfaz para pruebas de lógica e integración real junto con go test -race para protocolos, bloqueos y carreras. La prueba debe demostrar que no queda ninguna tarea de la burbuja al salir.”
Errores comunes
- Mantener
time.Sleepdentro de la burbuja → las pruebas siguen siendo lentas y no deterministas → avanza el tiempo virtual. - Hacer aserciones solo sobre el resultado final → los reintentos y la limpieza por cancelación pueden ser incorrectos → valida temporizadores, llamadas y callbacks paso a paso.
- Iniciar goroutines fuera de la burbuja → no se puede esperar la convergencia → haz que la burbuja sea dueña del trabajo.
- Tratar la red real como tiempo virtual → la I/O sigue siendo inestable → usa fakes y pruebas de integración separadas.
- Ignorar las diferencias de API entre 1.24/1.25 → las compilaciones locales y de CI divergen → fija Go y el flag experimental.
- Omitir el detector de carreras → la sincronización puede ser correcta mientras persisten carreras de datos → combina
go test -race. - Reintentar después de la cancelación → las solicitudes y el uso de recursos aumentan → verifica el contexto antes de cada espera y llamada.
- Usar un único
Waitfinal para todas las aserciones → los límites de fallo no quedan claros → avanza y verifica por fases.
Preguntas de seguimiento
Pregunta de seguimiento 1: ¿Synctest reemplaza las pruebas en tiempo real?
No. Verifica la lógica concurrente controlada y las relaciones temporales; los relojes, las redes, las bases de datos y los planificadores aún necesitan pruebas en entornos reales.
Pregunta de seguimiento 2: ¿Por qué llamar a Wait?
Avanzar el tiempo virtual hace expirar los temporizadores; Wait ejecuta las goroutines listas en la burbuja hasta que alcanzan el estado de reposo, haciendo que las aserciones sean estables.
Pregunta de seguimiento 3: ¿Cómo pruebas un bloqueo permanente?
Asigna un plazo límite a la operación, avanza hasta él y valida el error y la limpieza. Para bloqueos externos que no se pueden cancelar, usa un fake o una prueba de tiempo de espera aislada en lugar de esperar indefinidamente dentro de la burbuja.
Pregunta de seguimiento 4: ¿Se puede utilizar el experimento de Go 1.24 en producción?
Puede respaldar pruebas controladas, pero el estado experimental, el flag y el riesgo de actualización deben ser explícitos. Para una compatibilidad estable, prefiere la API de Go 1.25 y fija el CI.
Pregunta de seguimiento 5: ¿Cómo encuentras fugas fuera de la burbuja?
Comprueba las señales de finalización y los recursos cerrados antes de salir, luego combina -race, perfiles de goroutines o tiempos de espera de integración. Synctest no es un detector de fugas completo a nivel de proceso.
Pregunta de seguimiento 6: ¿Cómo detectas desviaciones en el retroceso (backoff drift)?
Registra la marca de tiempo virtual de cada llamada y compárala con la secuencia esperada. Prueba también el truncamiento del plazo límite en la espera final y la cancelación temprana.