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?