He estado dando vueltas en círculos tratando de encontrar la mejor manera de probar la unidad de una biblioteca de cliente API que estoy desarrollando. La biblioteca tiene una Client
clase que básicamente tiene una asignación 1: 1 con la API, y una Wrapper
clase adicional que proporciona una interfaz más fácil de usar en la parte superior de la Client
.
Wrapper --> Client --> External API
La primera vez que escribí un montón de pruebas en contra de ambos Client
y Wrapper
, efectivamente haciendo una prueba de que siempre que presenten a las funciones apropiadas de cualquiera que sea la vez operan en ( Wrapper
opera en Client
, y Client
opera en una conexión HTTP). Sin embargo, comencé a sentirme incómodo con esto porque siento que estoy probando la implementación de estas clases, en lugar de la interfaz. En teoría, podría cambiar las clases para tener otra implementación perfectamente válida, pero mis pruebas fallarían porque no se llaman las funciones que esperaba que se llamaran. Eso me suena a pruebas frágiles.
Después de esto, pensé en la interfaz de las clases. Las pruebas deben verificar que las clases realmente hacen el trabajo que deben hacer, en lugar de cómo lo hacen. Entonces, ¿cómo puedo hacer esto? Lo primero que viene a la mente es tropezar las solicitudes de API externas. Sin embargo, estoy nervioso por simplificar demasiado el servicio externo. Muchos de los ejemplos de API apagadas que he visto solo dan respuestas enlatadas, lo que parece una forma realmente fácil de probar que su código se ejecuta correctamente contra su API falsa. La alternativa es burlarse del servicio, que no es factible y que debería mantenerse actualizado cada vez que cambie el servicio real, lo que se siente como una exageración y una pérdida de tiempo.
Finalmente, leí esto de otra respuesta en los programadores SE :
El trabajo de un cliente API remoto es emitir ciertas llamadas, ni más ni menos. Por lo tanto, su prueba debe verificar que emite esas llamadas, ni más ni menos.
Y ahora estoy más o menos convencido: cuando pruebo Client
, todo lo que necesito probar es que realiza las solicitudes correctas a la API (Por supuesto, siempre existe la posibilidad de que la API cambie, pero mis pruebas continúan aprobadas, pero eso es donde las pruebas de integración serían útiles). Dado que Client
es solo un mapeo 1: 1 con la API, mi preocupación antes de cambiar de una implementación válida a otra realmente no se aplica: solo hay una implementación válida para cada método Client
.
Sin embargo, todavía estoy atrapado con la Wrapper
clase. Veo las siguientes opciones:
Termino la
Client
clase y solo pruebo que se llamen los métodos apropiados. De esta manera, estoy haciendo lo mismo que antes pero tratando elClient
como un sustituto de la API. Esto me devuelve a donde comencé. Una vez más, esto me da la sensación incómoda de probar la implementación, no la interfaz. ElWrapper
podría muy bien implementarse usando un cliente completamente diferente.Yo creo un simulacro
Client
. Ahora tengo que decidir hasta dónde llegar con burlarse de él: crear una burla completa del servicio requeriría mucho esfuerzo (más trabajo del que se ha dedicado a la biblioteca). La API en sí es simple, pero el servicio es bastante complejo (es esencialmente un almacén de datos con operaciones sobre esos datos). Y nuevamente, tendré que mantener mi simulacro sincronizado con lo realClient
.Solo pruebo que se están realizando las solicitudes HTTP adecuadas. Esto significa que
Wrapper
llamará a través de unClient
objeto real para hacer esas solicitudes HTTP, por lo que en realidad no lo estoy probando de forma aislada. Esto lo convierte en una prueba unitaria terrible.
Así que no estoy particularmente contento con ninguna de estas soluciones. ¿Qué harías? ¿Hay una manera correcta de hacer esto?
Respuestas:
TLDR : a pesar de la dificultad, debe desactivar el servicio y utilizarlo para las pruebas de la unidad del cliente.
No estoy tan seguro de que "el trabajo de un cliente API remoto sea emitir ciertas llamadas, ni más, ni menos ...", a menos que la API solo consista en puntos finales que siempre devuelvan un estado fijo, y no consuman ni produzcan cualquier dato Esta no sería la API más útil ...
También querrá verificar que el cliente no solo envíe las solicitudes correctas, sino que maneje adecuadamente el contenido de respuesta, errores, redirecciones, etc. Y pruebe todos estos casos.
Como observa, debe tener pruebas de integración que cubran la pila completa desde el contenedor -> cliente -> servicio -> DB y más, pero para responder a su pregunta principal, a menos que tenga un entorno donde las pruebas de integración se puedan ejecutar como parte de cada Creación de CI sin muchos dolores de cabeza (bases de datos de prueba compartidas, etc.), debe invertir el tiempo en crear un trozo de la API.
El código auxiliar le permitirá crear una implementación funcional del servicio, pero sin tener que implementar ninguna capa debajo del servicio en sí.
Podría considerar usar una solución basada en DI para lograr esto, con una implementación del patrón de Repositorio debajo de los recursos REST:
De todos modos, a menos que descartar y / o refactorizar el servicio esté fuera de discusión, ya sea política o prácticamente, probablemente emprendería algo similar a lo anterior. Si no es posible, me aseguraría de tener una cobertura de integración realmente buena y ejecutarlas con la frecuencia que sea posible dada la configuración de prueba disponible.
HTH
fuente