Usando Copilot para desarrollar una API
He visto varias demos del uso de un Copilot para ayudarnos en el desarrollo de nuestras APIs, pero siempre tiendo a ver ejemplos sencillos realizando un CRUD de algún recurso sencillo y pidiéndole que desarrolle unas pruebas para el mismo, ejemplos sencillos. Me pregunto si me podría ayudar en un desarrollo menos ideal, quizás con operaciones que salgan un poco de lo que es el estandar REST, pero que se nos pueden dar en la vida real. Además, conocer de que manera prepararía pruebas para las mismas.
Una consideración previa sería ver que Copilot o que LLM utilizamos, ya que cada uno puede dar un resultado distinto y muy dispar. Mis primeras opciones serían: Bing Copilot (por estar integrado con el buscador) o Github Copilot (por ser más específico para programación). Voy a hacer las pruebas con este último.
Como quiero que los ejemplos que ponga sean accesibles, pondré las conversaciones en modo texto, indicando el rol.
Desarrollando la API
Le comienzo pidiendo que me ayude a arrancar el proyecto con el siguiente prompt sobre un documento vacio (para no darle ningún contexto):
1
2
3
```plaintext
User: Quiero desarrollar una API utilizando ASP.NET con las operaciones básicas de CRUD para un recurso que simboliza una factura.
```
A lo que me proporciona los siguiente pasos (resumidos): 1. Crear un nuevo proyecto de ASP.NET Core Web API. 2. Definir el modelo de Factura. 3. Crear el contexto de la base de datos. 4. Configurar el servicio de base de datos en Startup.cs. 5. Crear el controlador de Factura. 6. Ejecutar la aplicación.
Por comentar algunos puntos. Sugiere algunas cosas que no me encajan, como es un Identificador numérico, prefiero una cadena de texto. Y las propiedades son muy escasas, pero es un inicio, está bien. Respecto al controlador, no utiliza plural. En cuanto a las operaciones, correcto. Pero si vemos el primer juego que podemos hacer, ya que ofrece obtener todas las facturas u obtenerla por identificador, pero nos ha sugerido un campo “Número”, por el que podría ser interesante buscar. Si se lo pido, lo resuelve y nos ofrece un nuevo endpoint con la ruta /api/factura/buscar/{numero}
si le interpelo indicando que eso no cumple REST me ofrece un nuevo endpoint /api/factura/buscar
con un parámetro por query numero
, creo que debería de haber modificado el endpoint de GetAll.
En este caso, podemos ver que aún siendo un caso sencillo, no considero que me esté respondiendo adecuadamente, no está respetando los principios REST, aunque tampoco se lo he indicado expresamente.
La operación controvertida
Una operación que se nos podría ocurrir es la de marcar una factura como pagada y controlar su estado. Que si ya está en ese estado, no nos permita volver a hacerlo, pero… ¿Y si pedimos además un estado de “abonada”? ¿Qué nos ofrecerá Copilot para esta funcionalidad?
1
Usuario: Necesito que controlar dos estados en la factura, por un lado podré marcarla como "cobrada" y esto sólo puede darse una vez, si se encuentra ya en ese estado, el endpoint nos debe de dar un error. Otra funcionalidad es la capacidad de marcarla como "abonada". Está claro que si se encuentra en uno de estos estados no puede cambiar a ningún otro. ¿Cómo lo puedo hacer?
Su propuesta es la de añadir una nueva propiedad Estado:string
y dos nuevos endpoints /{id}/cobrar
y /{id}/abonar
. Aunque realiza las comprobaciones justas, se limita a las indicaciones que le he dado (lo que también, es normal, da lo que pido). El estado como string podría ser correcto, funciona, aunque no me parece el mejor tipo, un enumerado estaría mejor para evitar errores en las comprobaciones futuras y reducir errores al asignar valores en otras partes del código.
En cuanto al error que propone, en ambos casos se trata de un BadRequest
si la factura se encuentra en un estado que no permite la operación. Otra posibilidad habría sido un 409 Conflict
si la factura ya se encuentra en ese estado. Quizás este código es poco utilizado en general y muy específico, pero en este caso, me podría encajar y podría haber mostrado un punto más original.
Probando la API
Vamos a pedirle que me genere pruebas. El punto interesante de este apartado es ver que pruebas general, que estrategia usa para las mismas. Siempre hablamos de tipos de pruebas como integración, contrato, etc… pero no suelo ver que hablemos de cosas como pruebas de límite o de equivalencia, por eso, voy a darle un caso y voy a pedirle un número grande de pruebas para ver si me da el número que pido o es capaz de ver que sobran.
1
Tengo que hacer pruebas de creación de facturas, teniendo en cuenta que el "Monto" siempre ha de ser un número positivo y no aceptaré facturas con Monto mayor a 1000€. Necesito crear 7 pruebas que validen la creación de la factura.
Me ofrece información para añadir pruebas unitarias a los controladores utilizando xUnit, yo no le he indicado ninguna librería en concreto.
El punto importante es que realmente me da 7 pruebas:
- CreateFactura_ValidFactura_ReturnsCreatedAtRoute
- Monto = 500.00M
- CreateFactura_NegativeMonto_ReturnsBadRequest
- Monto = -100.00M
- CreateFactura_ZeroMonto_ReturnsBadRequest
- Monto = 0.00M
- CreateFactura_MontoGreaterThan1000_ReturnsBadRequest
- Monto = 1500.00M
- CreateFactura_MontoEqualTo1000_ReturnsCreatedAtRoute
- Monto = 1000.00M
- CreateFactura_MontoLessThan1000_ReturnsCreatedAtRoute
- Monto = 999.99M
- CreateFactura_InvalidModelState_ReturnsBadRequest
- Monto = 500.00M
Podemos ver que si me ha dado tantos casos como le he pedido, pero no ha resuelto las pruebas con las estrategias que antes mencioné. Ha intentado hacer pruebas de límites con 999.99
y 1000
, pero en este caso el límite a probar debería de ser 1000
y 1000.01
, ya que al probar 1500
podríamos tener el código mal, permitiendo facturas de hasta 1499.99
. En el caso de valores negativos ocurre lo mismo, prueba el 0
y el -100
, pero no prueba el -0.01
, que podría ser un caso interesante y da 0
como un valor no posible, cuando 0
es un posible valor positivo. En la prueba 7 se inventa una propiedad requerida Numero
, que aunque tiene sentido, no se ha indicado en ningún momento.
Conclusiones
En este artículo quería repasar un ejemplo básico, pero aplicando preguntas que no veo en general en los artículos de este tipo. Parece que siempre se parte del asombro de que haga algo que tiene sentido, sin preguntarse si realmente lo tiene. Muchos ejemplos se limitan a pedirle que nos desarrolle pruebas y las da, algunas sólo tenemos que corregir algunas cosas, pero ¿Realmente queremos tener pruebas por tener? Le he pedido 7, pero le podía haber pedido 50 y me las daría, ¿Mejora en algo eso nuestra API? No lo creo, y aquí es donde debemos marcar la diferencia con el uso de los Copilot, debe de ayudarnos a producir más rápido y mejor, pero no nos abstrae nunca de que debemos de tener el conocimiento de lo que necesitamos y donde pueden estar los problemas o los puntos críticos. Y aunque hiciese esa parte correctamente, debemos pensar y validar que lo que nos da es correcto, no convertirnos en meros picadores de la tecla tabulador
.