¿Cómo probar el código que depende de las API complejas (Amazon S3, por ejemplo)?

13

Estoy luchando con probar un método que carga documentos en Amazon S3, pero creo que esta pregunta se aplica a cualquier API / dependencia externa no trivial. Solo he encontrado tres posibles soluciones, pero ninguna parece satisfactoria:

  1. Ejecute el código, cargue el documento, verifique con la API de AWS que se ha cargado y elimínelo al final de la prueba. Esto hará que la prueba sea muy lenta, costará dinero cada vez que se ejecute la prueba y no siempre arrojará el mismo resultado.

  2. Simulacro S3. Esto es super peludo porque no tengo idea de lo interno de ese objeto y se siente mal porque es demasiado complicado.

  3. Solo asegúrese de que se llame a MyObject.upload () con los argumentos correctos y confíe en que estoy usando el objeto S3 correctamente. Esto me molesta porque no hay forma de saber con certeza si usé la API S3 correctamente solo de las pruebas.

Verifiqué cómo Amazon prueba su propio SDK y se burlan de todo. Tienen un ayudante de 200 líneas que se burla. No creo que sea práctico para mí hacer lo mismo.

¿Cómo puedo solucionar esto?

cargado de resortes
fuente
No es una respuesta, pero en la práctica usamos los tres enfoques que ha expuesto.
jlhonora

Respuestas:

28

Hay dos cuestiones que tenemos que mirar aquí.

La primera es que parece que estás viendo todas tus pruebas desde la perspectiva de la prueba unitaria. Las pruebas unitarias son extremadamente valiosas, pero no son los únicos tipos de pruebas. Las pruebas se pueden dividir en varias capas diferentes, desde pruebas unitarias muy rápidas hasta pruebas de integración menos rápidas e incluso pruebas de aceptación más lentas . (Puede haber incluso más capas rotas, como pruebas funcionales ).

El segundo es que está combinando llamadas a código de terceros con su lógica empresarial, creando desafíos de prueba y posiblemente haciendo que su código sea más frágil.

Las pruebas unitarias deben ser rápidas y deben ejecutarse con frecuencia. Burlarse de las dependencias ayuda a mantener estas pruebas ejecutándose rápidamente, pero potencialmente puede introducir agujeros en la cobertura si la dependencia cambia y el simulacro no lo hace. Su código podría romperse mientras sus pruebas aún se ejecutan en verde. Algunas bibliotecas burlonas lo alertarán si la interfaz de la dependencia cambia, otras no.

Las pruebas de integración, por otro lado, están diseñadas para probar las interacciones entre componentes, incluidas las bibliotecas de terceros. Los simulacros no deben usarse en este nivel de prueba porque queremos ver cómo interactúa el objeto real. Debido a que estamos utilizando objetos reales, estas pruebas serán más lentas y no las ejecutaremos con tanta frecuencia como nuestras pruebas unitarias.

Las pruebas de aceptación miran a un nivel aún más alto, comprobando que se cumplen los requisitos para el software. Estas pruebas se ejecutan contra todo el sistema completo que se implementaría. Una vez más, no se deben usar burlas.

Una pauta que las personas han encontrado valiosa con respecto a los simulacros es no imitar tipos que no son de su propiedad . Amazon posee la API de S3 para que puedan asegurarse de que no cambie debajo de ellos. Usted, por otro lado, no tiene estas garantías. Por lo tanto, si se burla de la API S3 en sus pruebas, podría cambiar y romper su código, mientras que todas sus pruebas se muestran verdes. Entonces, ¿cómo unimos el código de prueba que utiliza bibliotecas de terceros?

Bueno, nosotros no. Si seguimos la directriz, no podemos burlarnos de los objetos que no poseemos. Pero ... si poseemos nuestras dependencias directas, podemos burlarnos de ellas. ¿Pero cómo? Creamos nuestro propio contenedor para la API S3. Podemos hacer que se parezca mucho a la API S3, o podemos hacer que se ajuste más a nuestras necesidades (preferido). Incluso podemos hacerlo un poco más abstracto, digamos un en PersistenceServicelugar de un AmazonS3Bucket. PersistenceServicesería una interfaz con métodos como #save(Thing)y #fetch(ThingId), los tipos de métodos que nos gustaría ver (estos son ejemplos, en realidad es posible que desee diferentes métodos). Ahora podemos implementar una PersistenceServiceAPI alrededor de S3 (digamos a S3PersistenceService), encapsulándola lejos de nuestro código de llamada.

Ahora al código que llama a la API S3. Necesitamos reemplazar esas llamadas con llamadas a un PersistenceServiceobjeto. Usamos la inyección de dependencia para pasar nuestra PersistenceServiceal objeto. Es importante no pedir una S3PersistenceService, sino pedir una PersistenceService. Esto nos permite intercambiar la implementación durante nuestras pruebas.

Todo el código que solía usar la API S3 directamente ahora usa nuestro PersistenceService, y nuestro S3PersistenceServiceahora hace todas las llamadas a la API S3. En nuestras pruebas, podemos simularlo PersistenceService, ya que lo poseemos, y usar el simulacro para asegurarnos de que nuestro código haga las llamadas correctas. Pero ahora eso deja cómo probar S3PersistenceService. Tiene el mismo problema que antes: no podemos probarlo sin llamar al servicio externo. Entonces ... no lo probamos unitariamente. Nos podríamos burlarse de las dependencias de la API S3, pero esto nos daría poca o ninguna confianza adicional. En cambio, tenemos que probarlo en un nivel superior: pruebas de integración.

Esto puede sonar un poco problemático al decir que no deberíamos hacer una prueba unitaria de una parte de nuestro código, pero veamos lo que logramos. Teníamos un montón de código por todas partes que no pudimos probar unitario que ahora se puede probar unitario a través del PersistenceService. Tenemos nuestra biblioteca de terceros confinada a una sola clase de implementación. Esa clase debe proporcionar la funcionalidad necesaria para usar la API, pero no tiene ninguna lógica comercial externa asociada. Por lo tanto, una vez que está escrito, debe ser muy estable y no debe cambiar mucho. Podemos confiar en pruebas más lentas que no ejecutamos con tanta frecuencia porque el código es estable.

El siguiente paso es escribir las pruebas de integración para S3PersistenceService. Deben separarse por nombre o carpeta para que podamos ejecutarlos por separado de nuestras pruebas unitarias rápidas. Las pruebas de integración a menudo pueden usar los mismos marcos de prueba que las pruebas unitarias si el código es suficientemente informativo, por lo que no necesitamos aprender una nueva herramienta. El código real para la prueba de integración es lo que escribiría para su Opción 1.

cbojar
fuente
la pregunta es cómo ejecuta la integración o, más bien, las pruebas e2e para la API que expone. No puede burlarse del Servicio de persistencia por razones obvias. O entendí mal algo, o agregar otra capa entre la API de la aplicación y la API de AWS, no me da más que tener más tiempo para hacer pruebas unitarias
Yerken
@Yerken Como lo estoy pensando, estoy bastante seguro de que podría completar otra respuesta larga a esa pregunta. Eso incluso podría valer la pena para usted porque podría obtener algo más que mi respuesta.
cbojar
4

Necesitas hacer ambos.

Ejecutar, cargar y eliminar es una prueba de integración. Se interconecta con un sistema externo y, por lo tanto, se puede esperar que funcione lentamente. Probablemente no debería ser parte de cada una de las compilaciones que hace localmente, pero debería ser parte de una compilación de CI o de una compilación nocturna. Eso compensa la lentitud de esas pruebas y aún proporciona el valor de hacer que se prueben automáticamente.

También necesita pruebas unitarias que se ejecutan más rápidamente. Como generalmente es inteligente no depender demasiado de un sistema externo (por lo que puede intercambiar implementaciones o cambiar), probablemente debería intentar escribir una interfaz simple sobre S3 con la que pueda codificar. Simula esa interfaz en las pruebas unitarias para que puedas tener pruebas unitarias de ejecución rápida.

Las primeras pruebas verifican que su código realmente funcione con S3, la segunda prueba que su código llama correctamente al código que habla con S3.

JDT
fuente
2

Yo diría que depende de la complejidad de su uso de la API .

  1. Definitivamente necesita hacer al menos algunas pruebas que realmente invoquen la API S3 y confirmen que funcionó de principio a fin.

  2. Definitivamente, también debe realizar pruebas adicionales que en realidad no llaman a la API, por lo que puede probar su propio software adecuadamente sin invocar la API todo el tiempo.

La pregunta que queda es: ¿necesitas burlarte de la API?

Y creo que eso depende de cuánto hagas con él. Si solo está realizando una o dos acciones simples, no creo que deba tomarse la molestia de una maqueta. Estaría satisfecho con solo verificar mi uso de las funciones y hacer algunas pruebas en vivo.

Sin embargo, si su uso es más complejo, con diferentes escenarios y diferentes variables que podrían afectar los resultados, probablemente deba simularlo para realizar pruebas más exhaustivas.


fuente
1

Además de las respuestas anteriores, la pregunta principal es si (y cómo) quiere burlarse de la API S3 para sus pruebas.

En lugar de burlarse manualmente de las respuestas individuales de S3, puede aprovechar algunos marcos de burla existentes muy sofisticados. Por ejemplo, moto proporciona una funcionalidad muy similar a la API S3 real.

También puede echar un vistazo a LocalStack , un marco que combina las herramientas existentes y proporciona un entorno de nube local totalmente funcional (incluido S3) que facilita las pruebas de integración.

Aunque algunas de estas herramientas están escritas en otros lenguajes (Python), debería ser fácil activar el entorno de prueba en un proceso externo a partir de sus pruebas en, por ejemplo, Java / JUnit.

whummer
fuente