Cómo trazar el límite entre pruebas unitarias y pruebas de integración

· Actualizado el: · · Pruebas, Pruebas unitarias, Pruebas de integración, Diseño de pruebas, Desarrollo en Windows, C# / .NET

En el diseño de pruebas, lo que resulta discretamente difícil una y otra vez es decidir hasta dónde llevar las pruebas unitarias y a partir de dónde subir a las pruebas de integración.

Lo peligroso aquí son estos dos extremos:

  • Convertir todo en pruebas unitarias porque se quiere que corran rápido
  • Convertir todo en pruebas de integración porque se parecen más a lo real

El primero termina lleno de mocks y hace fácil pasar por alto los puntos que fallan en producción; el segundo tiende a producir un conjunto de pruebas lento y frágil. En la práctica, los ejes que hay que observar son bastante más claros.

  • ¿Lo que se quiere confirmar es la lógica propia o la conexión con el exterior?
  • ¿El significado se mantiene aunque se sustituya por un fake en memoria (in-memory)?
  • ¿El tema central es el comportamiento de la base de datos, los archivos, HTTP, el DI, la configuración, el framework o el sistema operativo?
  • ¿Se quiere ejecutar rápidamente una gran cantidad de patrones de entrada?

Cuando estos cuatro puntos quedan claros, resulta mucho más fácil trazar el límite entre pruebas unitarias y pruebas de integración.

Este artículo está escrito partiendo del diseño de pruebas automatizadas en C# / .NET. Los ejemplos de código usan xUnit, pero el criterio de decisión en sí no depende del framework. Está planteado de forma que se pueda trasladar directamente a JUnit o a pytest.

El contenido se basa en «Integration tests in ASP.NET Core» de Microsoft Learn 1, «Unit testing best practices for .NET», también de Microsoft Learn 2, y «The Practical Test Pyramid» de Martin Fowler 3, todos ellos accesibles a fecha de marzo de 2026. Para que las referencias no queden reducidas a simples números, en el cuerpo del artículo se mencionará también el nombre de la fuente. Las URL de los originales están recogidas en las referencias al final del artículo.

1. Primero, la conclusión

Dicho de forma bastante simplificada, pero útil para el trabajo diario, sería así:

  1. La lógica pura va en pruebas unitarias
  2. La conexión, el cableado, la conversión y las diferencias de entorno van en pruebas de integración
  3. Si algo se puede verificar con cualquiera de las dos, empiece por la prueba unitaria
  4. Es mejor que la prueba de integración acote el límite en lugar de volverse amplia y pesada

En una frase: la prueba unitaria es la «prueba del juicio» y la prueba de integración es la «prueba de la conexión».

Las cosas cuyo sentido queda completo sin recursos externos —como el cálculo de importes, las transiciones de estado, la validación de entradas, las condiciones de aprobación o la clasificación de excepciones— conviene llevarlas hacia las pruebas unitarias: son más rápidas, se rompen menos y permiten cubrir a fondo los patrones de entrada. Por otro lado, lo que «traiciona en el instante en que se conecta» —la ejecución de SQL, la serialización de JSON / CSV, el enrutamiento, el model binding, el registro en el DI, el bloqueo de archivos, los permisos, el registro de COM, 32 bits / 64 bits, STA / MTA— es más seguro dejarlo del lado de las pruebas de integración.

En Integration tests in ASP.NET Core de Microsoft Learn también se plantea que las pruebas de integración deben limitarse a escenarios de infraestructura importantes, y que conviene elegir la prueba unitaria siempre que sea suficiente.

2. Qué entendemos aquí por pruebas unitarias y de integración

Aquí distinguimos los términos de la siguiente manera.

Nivel Qué se confirma Configuración típica
Prueba unitaria La corrección de una única responsabilidad aislada Usa fake / mock / stub y corta los recursos externos
Prueba de integración La conexión entre varios componentes y el comportamiento que incluye infraestructura y framework Base de datos real, archivos reales, serializer real, host real, pipeline real, etc.
E2E / prueba funcional El flujo de usuario de la aplicación completa Aplicación ya desplegada, varios servicios, navegador real o procesos reales

En la guía de pruebas unitarias de .NET se explica que una buena prueba unitaria es fast / isolated / repeatable (rápida, aislada y repetible) y no depende de factores externos como el sistema de archivos o la base de datos. Para más detalle, Unit testing best practices for .NET resulta muy claro.

Además, la prueba de integración no se refiere únicamente a «una prueba pesada que obligatoriamente usa otro proceso u otro servidor». Incluso dentro del mismo proceso, si se conectan varios componentes reales y se confirma el comportamiento real del framework o de la infraestructura, eso ya se acerca a una prueba de integración.

Por ejemplo, al hacer una prueba unitaria de una controller action de ASP.NET Core, la documentación oficial plantea limitar el objetivo al juicio del propio cuerpo de la action, y tratar en pruebas de integración las interacciones del lado del framework como routing, model binding o filters. Para más detalle, resulta útil consultar Unit test controller logic in ASP.NET Core.

2.1. La diferencia entre fake, mock y stub

En la tabla anterior aparecen juntos fake, mock y stub, pero estos tres no son lo mismo. Como clasificación de los test doubles (los «dobles» que se usan en las pruebas), en este artículo se emplean con el siguiente sentido, siguiendo la distinción que hace Martin Fowler en «Mocks Aren’t Stubs». 4

Nombre Qué hace Uso típico
stub Un doble que solo devuelve un valor fijo. No verifica cómo se lo llama Cuando se quiere fijar una condición de entrada, como «el stock siempre es de 3 unidades»
mock Un doble que verifica la propia forma en que se lo llama. Hace assert sobre el número de llamadas y los argumentos Cuando se quiere confirmar algo como «que el guardado se llame exactamente una vez»
fake Una implementación ligera que reproduce el mismo comportamiento que lo real Un repositorio en memoria, un almacén de archivos en un directorio temporal, etc.

En resumen: el stub sustituye la entrada, el mock verifica la llamada y el fake es una implementación simplificada. Esta distinción resulta relevante en el capítulo 4. El síntoma de «hacen falta 7 mocks» significa, con precisión, que hay 7 objetivos de verificación de llamadas, y es una señal de que esa prueba no está confirmando un único juicio, sino la forma en que se conectan varios componentes.

2.2. Cómo se trata el E2E en este artículo

El tema de este artículo es, ante todo, el límite entre las pruebas unitarias y las pruebas de integración. El E2E / prueba funcional se incluye en la tabla anterior solo a efectos comparativos, pero no se trata en profundidad en el cuerpo del texto.

Solo para dejar clara su posición: en la idea de la pirámide de pruebas, se forma un triángulo en el que cuanto más abajo está la capa, más numerosas y rápidas son las pruebas, y cuanto más arriba está, menos numerosas y más lentas son. «The Practical Test Pyramid» de Martin Fowler es el punto de partida de este planteamiento. 3 Es decir, la proporción consiste en tomar las pruebas unitarias como base, colocar pruebas de integración en cada límite y limitar el E2E a los flujos principales. La forma concreta de distribuirlas se resume en la estructura de 3 capas del capítulo 7.

3. La tabla de decisión de un vistazo

Empecemos por la tabla más útil para el trabajo diario.

Lo que se quiere confirmar Prueba principal Nota
Cálculo de importes, descuentos, transiciones de estado, validación de entradas Prueba unitaria Se quiere cubrir a fondo los patrones de entrada
Clasificación de excepciones, selección del mensaje de error, decisión de si reintentar Prueba unitaria El sentido queda completo sin I/O real
SQL / conversión ORM del repository, transacciones Prueba de integración El tema central es el comportamiento de la base de datos real o del provider real
Serialización / deserialización de JSON / XML / CSV Prueba de integración Un fake difícilmente detecta desajustes en el wire format
Enrutamiento, model binding, filtros, middleware Prueba de integración Confirmación de la conexión con el framework
Transiciones de estado del ViewModel o el Presenter en WPF / WinForms Prueba unitaria Tiene sentido aunque no se levante la UI
El Binding real, el Dispatcher, el ciclo de vida de los controles, el message loop Prueba de integración o prueba de UI El tema central es el comportamiento del framework y de los hilos
Rutas de archivo, permisos, bloqueos, carpetas compartidas, códigos de salto de línea, codificación de caracteres Prueba de integración Se necesita el comportamiento real del sistema operativo y del sistema de archivos
Registro de COM, 32 bits / 64 bits, STA / MTA, carga de DLL Prueba de integración El tema central son las diferencias de entorno y los límites entre procesos
Arranque de la aplicación completa, verificación de extremo a extremo de los casos de uso principales E2E / smoke Basta con pocas pruebas

La clave para leer la tabla es preguntarse qué prueba está más cerca del «motivo por el que algo se rompe en producción». Decidir en función de la incertidumbre que se quiere reducir, en lugar de la ubicación del código, da resultados más estables.

4. Qué debe cubrir la prueba unitaria

Lo que encaja en la prueba unitaria es la responsabilidad cuyo sentido se mantiene aunque se elimine el mundo exterior.

Por ejemplo:

  • Reglas de negocio
  • Ramificaciones
  • Transiciones de estado
  • Validación de entradas
  • Clasificación de errores
  • Decisión de la política de reintentos
  • Cambios de estado del ViewModel / Presenter
  • La propia lógica de conversión

En particular, cuanto mayor sea el número de combinaciones, mayor es el valor de llevarlo hacia la prueba unitaria.

Por ejemplo:

  • Con cupón / sin cupón
  • Con stock / sin stock
  • Primer pedido / pedido recurrente
  • Administrador / usuario general
  • Valor normal / valor límite / valor inválido

Como en estos casos, cuantas más condiciones de ramificación haya, más pesado resulta cubrirlas todas con pruebas de integración. Aquí es más razonable desglosarlas con detalle mediante pruebas unitarias.

Además, en las pruebas unitarias es importante mantener los factores externos bajo control.

  • Inyectar la hora actual
  • Hacer que el GUID y los números aleatorios se puedan sustituir
  • No esperar con sleep
  • No tocar la base de datos ni los archivos reales
  • No salir a la red real

Cuando se respeta esto, las pruebas se vuelven bastante estables.

4.1. Cuando los mocks se multiplican en una prueba unitaria

Al intentar escribir una prueba unitaria, si se llega a esta situación:

  • Hacen falta 7 mocks
  • El setup es largo
  • El arrange es más largo que el propio cuerpo
  • No se ve qué se quiere confirmar

suele deberse a una de estas dos causas:

  1. Esa clase tiene demasiadas responsabilidades
  2. Se está forzando dentro de la prueba unitaria un cableado que en realidad debería confirmarse con una prueba de integración

El mock es una herramienta para cortar el mundo exterior, no una herramienta para demostrar que la conexión con lo real es correcta. Si se confunde esto, es fácil terminar en la situación de «todo está en verde, pero falla en producción».

4.2. Un ejemplo mínimo de prueba unitaria

Si nos quedamos solo en palabras, la explicación resulta abstracta, así que vamos a poner un ejemplo de código para cada lado del límite. Empecemos por el lado de la prueba unitaria. La transición de estado del ViewModel tiene sentido completo aunque no se levante la UI, así que este es el territorio de la prueba unitaria.

// .NET 8 / xUnit
// Objetivo: un ViewModel que no toca ningún recurso externo
public sealed class OrderViewModel
{
    public decimal Subtotal { get; set; }
    public bool IsMember { get; set; }

    public bool CanCheckout => Subtotal > 0m;
    public decimal Total => IsMember ? Subtotal * 0.9m : Subtotal;
}

public class OrderViewModelTests
{
    [Fact]
    public void 会員なら1割引きになる()
    {
        var viewModel = new OrderViewModel { Subtotal = 1000m, IsMember = true };

        Assert.Equal(900m, viewModel.Total);
    }

    [Theory]
    [InlineData(0, false)]
    [InlineData(1, true)]
    public void 小計が0なら確定できない(int subtotal, bool expected)
    {
        var viewModel = new OrderViewModel { Subtotal = subtotal };

        Assert.Equal(expected, viewModel.CanCheckout);
    }
}

Aquí no aparecen ni base de datos, ni archivos, ni HTTP. Por eso es rápida, se puede ejecutar en paralelo y con [Theory] se pueden añadir tantos patrones de entrada como se quiera. Llevar aquí el barrido exhaustivo de ramificaciones se refiere, en concreto, a esta forma de trabajar.

5. Los 4 límites que se suben a la prueba de integración

Los lugares que conviene subir a la prueba de integración se pueden organizar, a grandes rasgos, en cuatro: el formato, el cableado, el entorno y el tiempo.

5.1. El límite del formato

En lo que aquí llamamos formato entra, entre otras cosas:

  • JSON / XML / CSV
  • El schema y el mapping de la base de datos
  • nullable / precision / timezone
  • La serialización de enum y de fechas
  • La codificación de caracteres y el BOM
  • El código de salto de línea

Martin Fowler también señala que el límite en el que interviene serialize / deserialize es candidato a prueba de integración. Para más detalle, resulta útil consultar The Practical Test Pyramid.

Por ejemplo:

  • Al convertir un DTO a JSON, el nombre del campo resultó distinto
  • Las comillas o los saltos de línea del CSV se rompieron
  • El decimal se redondeó
  • El tratamiento de DateTimeOffset se desajustó en la base de datos
  • El tratamiento de null y de la cadena vacía no fue el esperado

Fallos como estos se escapan fácilmente si solo se usan pruebas unitarias.

5.2. El límite del cableado

En el límite del cableado entran, por ejemplo, partes como estas:

  • El registro en el DI
  • El bind de la configuración
  • El enrutamiento
  • El model binding
  • Los filtros
  • middleware
  • El arranque del host
  • El cableado de eventos
  • El Binding de WPF o la conexión de comandos

Aquí el tema central no es «si mi propia función es correcta», sino si varios componentes reales están correctamente conectados.

En ASP.NET Core existe un planteamiento oficial según el cual la prueba unitaria de una controller action se limita al juicio de la action, mientras que routing, model binding y filters se revisan del lado de la prueba de integración. La idea es la misma aunque no sea para web: en una aplicación de escritorio, la transición de estado del ViewModel es prueba unitaria, y el comportamiento que incluye el Binding real de XAML o el Dispatcher se acerca a la prueba de integración.

5.3. El límite del entorno

En el desarrollo para Windows, este punto es bastante importante.

  • Permisos de archivo
  • Carpetas compartidas
  • Bloqueo de archivos
  • El rename desde un archivo temporal
  • Privilegios de administrador
  • Permisos de arranque de servicios
  • Registro de COM
  • 32 bits / 64 bits
  • STA / MTA
  • El origen de carga de las DLL

En estos casos, el protagonista es la propia condición del sistema operativo o del entorno de ejecución. Con un fake en memoria el sentido se pierde bastante, así que es más seguro cubrirlos con pruebas de integración.

En particular, en configuraciones que incluyen software Windows ya existente o COM / ActiveX, es habitual tropezar antes por el registro, la bitness, el modelo de hilos o los permisos que por la propia lógica. Este tipo de fallos es terreno de la prueba de integración que incluye el entorno, no de la prueba unitaria.

5.4. El límite del tiempo

Otro punto que se pasa por alto fácilmente es el tiempo y la concurrencia.

  • timeout
  • cancellation
  • El comportamiento real del retry
  • Lo impulsado por timer
  • La detención del procesamiento en segundo plano
  • race condition
  • El orden de cierre durante el shutdown

Lo importante aquí es separar el juicio del comportamiento real.

Por ejemplo:

  • Hasta cuántas veces reintentar
  • Qué excepciones son objeto de retry

son suficientes con la prueba unitaria. Por otro lado:

  • Si el timeout realmente surte efecto
  • Si la cancellation se propaga
  • Si no se rompe cuando el timer choca con el procesamiento asíncrono
  • Si al finalizar los handles y las tareas se cierran limpiamente

se acercan más a la prueba de integración.

5.5. Un ejemplo mínimo de prueba de integración

Con el mismo tema del apartado 4.2, vamos a escribir el otro lado del límite. Lo que se quiere confirmar aquí es si «el importe guardado no se rompe al ir y volver por la base de datos». Se pasa por un archivo SQLite real.

// .NET 8 / xUnit / Microsoft.Data.Sqlite
using System.Globalization;
using Microsoft.Data.Sqlite;

public sealed class OrderRepository(SqliteConnection connection)
{
    public void Save(int id, decimal total)
    {
        using var command = connection.CreateCommand();
        command.CommandText = "INSERT INTO orders (id, total) VALUES ($id, $total);";
        command.Parameters.AddWithValue("$id", id);
        command.Parameters.AddWithValue("$total", total.ToString(CultureInfo.InvariantCulture));
        command.ExecuteNonQuery();
    }

    public decimal FindTotal(int id)
    {
        using var command = connection.CreateCommand();
        command.CommandText = "SELECT total FROM orders WHERE id = $id;";
        command.Parameters.AddWithValue("$id", id);
        var stored = (string)command.ExecuteScalar()!;
        return decimal.Parse(stored, CultureInfo.InvariantCulture);
    }
}

public sealed class OrderRepositoryTests : IDisposable
{
    private readonly string _databasePath =
        Path.Combine(Path.GetTempPath(), $"orders-{Guid.NewGuid():N}.db");
    private readonly SqliteConnection _connection;

    public OrderRepositoryTests()
    {
        // Si no se deja Pooling=False, a veces no se puede borrar el archivo db al limpiar
        _connection = new SqliteConnection($"Data Source={_databasePath};Pooling=False");
        _connection.Open();

        using var create = _connection.CreateCommand();
        create.CommandText = "CREATE TABLE orders (id INTEGER PRIMARY KEY, total TEXT NOT NULL);";
        create.ExecuteNonQuery();
    }

    [Fact]
    public void 保存した金額が丸められずに読み戻せる()
    {
        var repository = new OrderRepository(_connection);

        repository.Save(id: 1, total: 1234.56m);

        Assert.Equal(1234.56m, repository.FindTotal(1));
    }

    public void Dispose()
    {
        _connection.Dispose();
        File.Delete(_databasePath);
    }
}

Lo que confirma esta prueba no son las ramificaciones de OrderRepository. Es la parte de la conexión: si el SQL se ejecuta, si el tipo de la columna corresponde correctamente con decimal, y si el valor se conserva al ir y volver. Como SQLite no tiene un tipo decimal, la pregunta de «con qué tipo hay que guardar para que no se rompa al ir y volver» deja de ser un problema de implementación y pasa a ser un problema de diseño de la conexión. Esto no se ve con un fake en memoria.

En este ejemplo se crea un archivo de base de datos temporal por cada clase de prueba y se borra en Dispose. Como la prueba de integración conserva estado, dejar claro cada vez dónde se crea y dónde se descarta es más importante que en la prueba unitaria.

6. Errores de criterio habituales

6.1. Conformarse con hacer mock del repository

Aunque se cubra todo lo relacionado con el repository con mocks, no se sabe:

  • Si el SQL es correcto
  • Si la transacción surte efecto
  • Si coincide con el schema
  • Si el mapping no se desajusta
  • Si la codificación de caracteres o la precisión no se rompen

El repository suele ser, más que objeto de prueba de lógica, un punto de conexión del límite. En ese caso, es más realista aumentar el peso de la prueba de integración frente a la unitaria.

6.2. Intentar cubrir también el framework en la prueba unitaria de un controller o endpoint

Lo que se quiere observar en la prueba unitaria de una controller action es, más o menos:

  • Las ramificaciones condicionales
  • La selección del valor de retorno
  • La forma de distinguir la llamada a los servicios dependientes

Por otro lado:

  • Si la route coincide
  • Si el model binding se completa
  • Si el filter surte efecto
  • Cómo se ve el resultado al pasar por el middleware

corresponde al lado de la prueba de integración. Si se mezclan, resulta difícil saber qué se rompió.

6.3. Barrer todos los patrones de entrada con pruebas de integración

La prueba de integración, precisamente por parecerse a lo real, resulta inevitablemente lenta. Por eso conviene separar: el barrido exhaustivo de ramificaciones va en la prueba unitaria, y los casos representativos del límite van en la prueba de integración.

En la explicación de pruebas de integración de Microsoft Learn también se recomienda, para la base de datos y el sistema de archivos, no ejecutar todos los patrones con pruebas de integración, sino limitarse a escenarios representativos como read / write / update / delete.

6.4. Golpear directamente el entorno de producción de un servicio externo desde CI

Esto es mejor evitarlo por seguridad.

En la prueba de integración importa el «parecido a lo real», pero eso no significa que haya que golpear cada vez el SaaS o la API de producción. Fowler también recomienda levantar los servicios externos en local, colocar un fake, o usar una test instance dedicada.

En la práctica, resulta manejable combinar:

  • Base de datos local
  • Directorio temporal
  • test host
  • Un test environment dedicado
  • Un fake service con el contrato fijado

7. Estructura recomendada para el trabajo diario

No existe una proporción absolutamente correcta. Sin embargo, las siguientes 3 capas se pueden usar de forma bastante general.

Capa Predominante Qué se coloca
Capa núcleo Pruebas unitarias en profundidad Reglas de negocio, transiciones de estado, validación de entradas, clasificación de errores
Capa de límites Pruebas de integración acotadas Base de datos, archivos, HTTP, serializer, DI, configuración, COM, permisos
Capa global Un número reducido de smoke / E2E Verificación de arranque, flujos principales, prevención de la repetición de fallos graves

En sensación general, lo que se hace grueso en cantidad es la prueba unitaria, y lo que se hace grueso en densidad de límites es la prueba de integración.

El procedimiento recomendado es este:

  1. Primero, enumerar los límites de la aplicación
  2. Llevar la lógica hacia una forma que se pueda separar del exterior
  3. Colocar, en cada límite, «al menos un happy path» y un «failure path representativo»
  4. Limitar el número de pruebas de extremo a extremo
  5. Cuando aparezca un bug, añadir la prueba en la capa donde ese bug se pueda reproducir con el menor coste

El punto 5, el último, es el importante.

  • Si es un error de reglas, añadir una prueba unitaria
  • Si es un error de SQL / binding / configuración / permisos / registro, añadir una prueba de integración
  • Si es un fallo que incluye el arranque o la distribución, añadir smoke o E2E

Si se añaden pruebas de esta manera, la responsabilidad de cada prueba se mantiene estable.

8. Las 5 preguntas finales para cuando hay dudas

Por último, resumimos 5 preguntas para usar como comprobación cuando surjan dudas.

  1. ¿El sentido que se quiere confirmar se mantiene aunque se sustituya por un fake en memoria (in-memory)?
    • Si se mantiene, se acerca a la prueba unitaria.
  2. Cuando algo se rompe, ¿lo que se sospecha no es la lógica sino la conexión o la configuración?
    • Si es así, se acerca a la prueba de integración.
  3. ¿El tema central no es la base de datos, los archivos, el serializer, el DI, la route, el model binding, el sistema operativo, los permisos, la bitness o los hilos?
    • Si es así, se acerca a la prueba de integración.
  4. ¿Se quiere ejecutar rápidamente una gran cantidad de patrones de entrada?
    • Si es así, se acerca a la prueba unitaria.
  5. Cuando esa prueba falla, ¿se sabe de inmediato qué hay que arreglar?
    • Si no se sabe, las capas de la prueba están mezcladas.

Ordenando con estas 5 preguntas resulta más fácil evitar decisiones vagas del tipo «prueba de integración porque se parece un poco más a lo real» o «prueba unitaria porque es un poco más rápida».

9. Resumen

Lo más práctico es decidir el límite entre pruebas unitarias y pruebas de integración no por la ubicación del código, sino por qué incertidumbre se quiere reducir.

Los puntos clave se reducen a estos 5:

  • La prueba unitaria es la prueba del juicio
  • La prueba de integración es la prueba de la conexión
  • El barrido exhaustivo de ramificaciones va en la prueba unitaria
  • El formato, el cableado, el entorno y el tiempo van en la prueba de integración
  • La verificación de extremo a extremo se cubre con un número reducido de smoke / E2E

Lo que más conviene evitar es:

  • Creer que un mock ya demuestra también la conexión con lo real
  • Intentar cubrir todas las ramificaciones con pruebas de integración
  • Mezclar la responsabilidad de la prueba unitaria con la de la prueba de integración

Si tiene dudas, observe antes que nada si ese fallo rompe el «juicio» o rompe la «conexión». Con esta única pregunta se pueden ordenar bastantes casos.

10. Artículos relacionados

11. Referencias

Artículos recientes con las mismas etiquetas para profundizar en temas cercanos.

Estas páginas sitúan el tema en un contexto más amplio de servicios y decisiones.

El artículo está directamente relacionado con los siguientes servicios.

Preguntas frecuentes

Preguntas habituales en las consultas sobre el tema del artículo.

¿Cómo se debe distinguir el uso de las pruebas unitarias y las pruebas de integración?
En una frase, la prueba unitaria es la «prueba del juicio» y la prueba de integración es la «prueba de la conexión». Las cosas cuyo sentido queda completo sin recursos externos, como el cálculo de importes, las transiciones de estado, la validación de entradas o la clasificación de excepciones, se llevan hacia la prueba unitaria; y lo que «traiciona en el instante en que se conecta», como la ejecución de SQL, la serialización de JSON/CSV, el enrutamiento, el registro en el DI, el bloqueo de archivos, los permisos, el registro de COM o 32bit/64bit, se coloca del lado de la prueba de integración. Si algo se puede verificar con cualquiera de las dos, empiece siempre por la prueba unitaria.
¿Qué tipo de cosas conviene subir a la prueba de integración?
En general se pueden organizar en cuatro límites: formato, cableado, entorno y tiempo. El formato incluye el JSON/CSV, el mapping de la base de datos o la codificación de caracteres; el cableado es la conexión de componentes reales como el registro en el DI, el enrutamiento o el model binding; el entorno es el comportamiento real del sistema operativo, como los permisos de archivo, el registro de COM, 32bit/64bit o STA/MTA; y el tiempo abarca el timeout, la cancellation o las race condition. Como en todos estos casos el sentido se pierde con un fake en memoria, es más seguro cubrirlos con pruebas de integración.
¿Qué problema hay en que los mocks se multipliquen en una prueba unitaria?
Si se llega a una situación en la que hacen falta 7 mocks, el setup es largo y no se ve qué se quiere confirmar, suele deberse a que esa clase tiene demasiadas responsabilidades, o a que se está forzando dentro de la prueba unitaria un cableado que en realidad debería confirmarse con una prueba de integración. El mock es una herramienta para cortar el mundo exterior, no una herramienta para demostrar que la conexión con lo real es correcta. Si se confunde esto, es fácil terminar en la situación de «todo está en verde, pero falla en producción».
¿Con qué estructura y proporción conviene organizar las pruebas?
Lo que se puede usar de forma general es una estructura de 3 capas. La capa núcleo mantiene en profundidad las reglas de negocio y las transiciones de estado con pruebas unitarias; la capa de límites coloca pruebas de integración acotadas en la base de datos, los archivos, el serializer o el DI; y la capa global cubre la verificación de arranque y los flujos principales con un número reducido de smoke/E2E. El barrido exhaustivo de ramificaciones se ejecuta con pruebas unitarias, y la prueba de integración se limita, en cada límite, a al menos un happy path y a un failure path representativo. Cuando aparece un bug, se añade la prueba en la capa donde ese bug se pueda reproducir con el menor coste.

Perfil del autor

Página de presentación del autor del artículo.

Go Komura

Representante de KomuraSoft LLC

Especializado en desarrollo de software para Windows, consultoría técnica e investigación de fallos, sobre todo en proyectos con sistemas existentes y errores difíciles de reproducir.

Volver al blog