Desarrollo controlado por pruebas para Spring Boot APIs

Introducción

Con el aumento en la adopción de teléfonos inteligentes en el mundo actualmente, ha habido una afluencia de aplicaciones móviles para lograr una amplia variedad de tareas. Algunas de las aplicaciones que utilizamos a diario se comunican con otros sistemas para brindarnos una experiencia perfecta en múltiples dispositivos y plataformas.
¿Cómo es esto posible? Las interfaces de programación de aplicaciones (API) son responsables de esta conectividad extendida. Permiten que las aplicaciones móviles y web interactúen y facilitan la transferencia de datos entre ellos y otros sistemas.
En este artículo, analizaremos las API, las mejores prácticas al crearlas y también construiremos una API utilizando el enfoque de desarrollo dirigido por pruebas y el marco de Spring Boot .

El ascenso de las API

Una API define un conjunto de rutinas y protocolos para la interacción entre los sistemas de software. Muchas aplicaciones móviles y web interactúan con los servidores que manejan las solicitudes y responden a ellas, denominadas clientes .
A medida que los sistemas crecen en tamaño, se vuelven robustos y pueden volverse difíciles de mantener y hacer actualizaciones. Al desacoplar un sistema en varias API específicas, se logra la flexibilidad y las partes del sistema robusto ahora se pueden actualizar o implementar en partes fácilmente sin afectar el tiempo de funcionamiento o el rendimiento del resto del sistema.
Esto da como resultado una arquitectura de microservicios, que depende en gran medida del desarrollo de API. En tal sistema, las API proporcionan un modo de comunicación dentro del sistema y las diversas partes del sistema aún pueden interactuar y compartir la carga de trabajo.
Los teléfonos inteligentes nos permitieron mantenernos conectados y con su creciente poder, podemos lograr mucho más. El acceso a Internet también se ha vuelto más común, por lo que la mayoría de los teléfonos inteligentes están constantemente conectados a Internet. Estos dos factores impulsan el uso de aplicaciones móviles que interactúan con servidores web donde las API entran en escena.
Las API facilitan la comunicación entre las aplicaciones móviles y los servidores, y el aumento en el uso de las aplicaciones móviles ha impulsado el aumento de las API.
Las aplicaciones web también han evolucionado con el tiempo y la complejidad ha aumentado. Esto, a su vez, ha llevado a la separación de las capas de presentación y lógica de una aplicación web normal. Inicialmente, tendría ambas capas de una aplicación web integradas y desplegadas como una para el uso de las masas. Ahora, la sección de frontend está desacoplada del backend para facilitar la separación de preocupaciones.
Las API también permiten a las empresas realizar una única configuración de backend para servir aplicaciones móviles y aplicaciones web al mismo tiempo. Esto ahorra tiempo de desarrollo y deuda técnica, ya que el sistema backend solo se modifica en un punto.
Los teléfonos inteligentes también son tan diversos y las compañías ahora tienen que atender múltiples tipos de teléfonos inteligentes al mismo tiempo para brindar una experiencia uniforme a sus usuarios. Las API hacen posible que las aplicaciones móviles que se ejecutan en diferentes plataformas interactúen de manera uniforme con un solo sistema backend o API.
Es muy importante mencionar que las API también hacen posible que otros desarrolladores que utilizan diferentes lenguajes de programación puedan acceder a nuestro sistema para obtener información. Esto facilita la integración de sistemas que utilizan diferentes lenguajes de programación.
Esto, nuevamente, nos permite hacer aplicaciones modulares, utilizando varios lenguajes, herramientas y marcos juntos para sacar lo mejor de cada uno.

Construyendo mejores APIs

Las API también actúan como punto de contacto con el trabajo de otros desarrolladores, ya que pueden permitir que otros desarrolladores las consuman para su propio uso.
Por ejemplo, Twitter ha expuesto algunas de sus API para el uso de otros desarrolladores para crear otros clientes de Twitter y usar la plataforma de otras formas únicas. Algunos han creado robots en plataformas como Telegram para enviar tweets o buscar tweets, todo lo cual se logra a través de las API.
Esto hace que las API sean importantes en los ecosistemas de software actuales y futuros, ya que nos permiten integrarnos con otros sistemas de manera flexible. No solo APIs, sino buenas APIs.
Es de suma importancia que nuestra API esté bien construida y documentada para que cualquier otra persona que la consuma tenga un tiempo más fácil. La documentación es el aspecto más importante de una API, ya que permite a otros desarrolladores saber qué realiza y qué se necesita para aprovechar esa funcionalidad. También ayuda a los mantenedores a saber con qué están lidiando ya asegurarse de que sus cambios no afecten o rompan la funcionalidad existente.
Los códigos de estado HTTP se definieron para identificar diversas situaciones que pueden ocurrir cuando una aplicación está interactuando con una API.
Se dividen en cinco categorías que incluyen códigos para:
  • Respuestas informativas : estados 1xx , como 100 Continuar , 101 Protocolos de conmutación , etc.
  • Éxito : estados 2xx , como 200 OK , 202 Aceptados , etc.
  • Redirección : estados 3xx , como 300 opciones múltiples , 301 movidos permanentemente , etc.
  • Errores del cliente : estados 4xx , como 400 Solicitud incorrecta , 403 Prohibido , 404 No encontrado , etc.
  • Errores del servidor : estados 5xx , como 500 Error interno del servidor , 502 Puerta de enlace incorrecta , 503 Servicio no disponible , etc.
Estos códigos ayudan al sistema y a las personas que interactúan con él a identificar y comprender la naturaleza de los eventos que ocurren y las causas de cualquier error.
Al adherirnos a los códigos de estado HTTP en nuestras API, podemos hacer que nuestras API sean fáciles de interactuar e integrar. Además de estos, también podemos definir nuestros propios códigos de error para nuestras API, pero es importante que los documentemos claramente para que sea más fácil para los consumidores y mantenedores de las API.
Antes de que los automóviles o los teléfonos o los dispositivos electrónicos sean entregados a sus usuarios, se prueban exhaustivamente para garantizar que no funcionen mal cuando están en uso. Las API se han vuelto más comunes e importantes, por lo tanto, también necesitan la misma atención al detalle.
Deben probarse exhaustivamente antes de ser liberados para evitar un funcionamiento defectuoso durante la producción.

Construyendo una API

Proyecto de arquitectura

Supongamos que estamos construyendo una aplicación que ayuda a los usuarios a mantener una lista de sus autos. Podrán agregar autos nuevos, actualizar autos existentes e incluso eliminar autos que ya no poseen. Esta aplicación estará disponible para dispositivos Android e iOS y también como una aplicación web.
Usando Spring Boot Framework, podemos crear una API única que pueda servir a las tres aplicaciones, o clientes, simultáneamente.
Nuestro viaje comienza en la herramienta Spring Initializer que nos ayuda a reiniciar rápidamente nuestra API Spring Boot en cuestión de minutos. Hay muchas dependencias y paquetes que nos ayudan a lograr varias funcionalidades en nuestras API y la herramienta Spring Initializer ayuda a integrarlos en nuestro proyecto inicial.
Está dirigido a facilitar nuestro proceso de desarrollo y permitirnos dirigir nuestra atención a la lógica de nuestra aplicación:
Primavera Initializr
La herramienta nos permite elegir entre Maven y Gradle , que son herramientas que nos ayudan a automatizar algunos aspectos de nuestro flujo de trabajo de compilación, como probar, ejecutar y empaquetar nuestra aplicación Java. También tenemos la opción de elegir entre usar Java o Kotlin al crear nuestra API usando Spring Boot, para lo cual podemos especificar la versión.
Cuando hacemos clic en "Cambiar a la versión completa" obtenemos más opciones para integrarse en nuestra API. Muchas de estas opciones son útiles cuando se crean microservicios como las secciones "Configuración de la nube" y "Descubrimiento de la nube".
Para nuestra API, elegiremos las siguientes dependencias:
  • Web para ayudarnos a desarrollar una API basada en web
  • MySQL lo que nos ayudará a conectarnos a nuestra base de datos MySQL,
  • JPA que es la API de persistencia de Java para satisfacer nuestras necesidades de interacción con la base de datos, y
  • Actuator Para ayudarnos a mantener y monitorear nuestra aplicación web.
Con las dependencias establecidas, hacemos clic en el botón "Generar proyecto" para obtener un código postal que contiene nuestro código de referencia.
Identifiquemos lo que viene en el paquete usando el treecomando:
$ tree .
.
├── HELP.md
├── mvnw
├── mvnw.cmd
├── pbcopy
├── pom.xml
└── src
    ├── main
    │   ├── java
    │   │   └── com
    │   │       └── example
    │   │           └── cars
    │   │               └── CarsApplication.java
    │   └── resources
    │       ├── application.properties
    │       ├── static
    │       └── templates
    └── test
        └── java
            └── com
                └── example
                    └── cars
                        └── CarsApplicationTests.java
En la carpeta raíz, hay un pom.xmlarchivo que contiene la configuración del proyecto para nuestra API Spring Boot. Si utilizáramos Gradle, tendríamos un build.gradlearchivo en su lugar. Incluye información como los detalles de nuestra nueva API y todas sus dependencias.
Trabajaremos principalmente en las carpetas maintestdentro de la srccarpeta de origen ( ). Aquí es donde colocaremos nuestros controladores, modelos, clases de utilidad, entre otros.
Comencemos por crear nuestra base de datos y configurar nuestra API para usarla. Siga esta guíapara instalar y verificar que MySQL se está ejecutando.
Una vez listo, creamos nuestra base de datos de la siguiente manera:
$ mysql -u root -p

mysql> CREATE DATABASE cars_database;  
Query OK, 1 row affected (0.08 sec)  
Algunos detalles de nuestro servicio serán diferentes de un entorno a otro. Por ejemplo, la base de datos que utilizamos durante el desarrollo no será la misma que los usuarios finales utilizarán para almacenar su información.
Los archivos de configuración nos facilitan el cambio de estos detalles, lo que hace que nuestra API sea fácil de migrar y modificar. Esto se logró a través del archivo de configuración, que en una API Spring Boot es el application.propertiesarchivo que se encuentra en la src/main/resourcescarpeta.
Para permitir que nuestra dependencia JPA acceda y modifique nuestra base de datos, modificamos el archivo de configuración agregando las propiedades:
# Database Properties
spring.datasource.url = jdbc:mysql://localhost:3306/cars_database?useSSL=false  
spring.datasource.username = root  
spring.datasource.password = password

# Hibernate Properties
# The SQL dialect makes Hibernate generate better SQL for the chosen database
spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MySQL5InnoDBDialect

# Hibernate ddl auto (create, create-drop, validate, update)
spring.jpa.hibernate.ddl-auto = update  
Ahora necesitamos una clase de entidad para definir los recursos de nuestra API y sus detalles, ya que se guardarán en nuestra base de datos. Cares nuestro recurso en esta API y lo que esto significa es que representa nuestro objeto o elemento de la vida real cuya información realizaremos acciones. Tales acciones incluyen Crear, Leer, Actualizar y Eliminar, simplemente poner como operaciones CRUD .
Estas operaciones están detrás de los Métodos o Verbos HTTP que se refieren a varias operaciones que una API puede exponer. Incluyen:
  • GET que es una operación de lectura que solo recupera los datos especificados,
  • POSTlo que permite la creación de, resourcesproporcionando su información como parte de la solicitud,
  • PUT lo que nos permite modificar un recurso, y
  • DELETE que utilizamos para eliminar un recurso y su información de nuestra API.
Para organizar mejor nuestro código, presentaremos algunas carpetas más en nuestro proyecto en el src/main/java/com/example/cars/nivel. Agregaremos una carpeta llamada modelspara alojar las clases que definen nuestros objetos.
Las otras carpetas que se agregarán incluyen una controllerscarpeta que contiene nuestros controladores, una repositorycarpeta para las clases de administración de bases de datos y una utilscarpeta para cualquier clase de ayuda que podamos necesitar para agregar a nuestro proyecto. La estructura de carpetas resultante será:
$ tree .
.
├── HELP.md
├── mvnw
├── mvnw.cmd
├── pbcopy
├── pom.xml
└── src
    ├── main
    │   ├── java
    │   │   └── com
    │   │       └── example
    │   │           └── cars
    │   │               ├── CarsApplication.java
    │   │               ├── controllers
    │   │               ├── models
    │   │               ├── repository
    │   │               └── utils
    │   └── resources
    │       ├── application.properties
    │       ├── static
    │       └── templates
    └── test
        └── java
            └── com
                └── example
                    └── cars
                        └── CarsApplicationTests.java

Modelo de dominio

Vamos a definir nuestra Carclase en la modelscarpeta:
/**
* This class will represent our car and its attributes
*/
@Entity
@Table(name="cars") // the table in the database tht will contain our cars data
@EntityListeners(AuditingEntityListener.class)
public class Car {

    @Id
    @GeneratedValue(strategy=GenerationType.AUTO)
    private long id; // Each car will be given an auto-generated unique identifier when stored

    @Column(name="car_name", nullable=false)
    private String carName; // We will also save the name of the car

    @Column(name="doors", nullable=false)
    private int doors; // We will also save the number of doors that a car has

    // getters and setters
}
Nota : he eliminado las importaciones para acortar el fragmento de código. Consulte el repositorio de Github adjunto al final del artículo para obtener el código completo.

DAO

Con nuestro modelo de automóvil listo, creamos ahora el CarRepositoryarchivo que se utilizará en la interacción con la base de datos:
public interface CarRepository extends JpaRepository<Car, Long> { }  

Pruebas de escritura

Ahora podemos exponer la funcionalidad de nuestra API a través de nuestro controller, pero en el espíritu de Desarrollo dirigido por pruebas (TDD), escribamos las pruebas primero en el CarsApplicationTestsarchivo:
// These are a subset of the tests, the full test file is available on the Github repo attached at the end of this article
....

    /**
     * Here we test that we can get all the cars in the database
     * using the GET method
     */
    @Test
    public void testGetAllCars() {
        HttpHeaders headers = new HttpHeaders();
        HttpEntity<String> entity = new HttpEntity<String>(null, headers);

        ResponseEntity<String> response = restTemplate.exchange(getRootUrl() + "/cars",
            HttpMethod.GET, entity, String.class);

        Assert.assertNotNull(response.getBody());
    }

    /**
     * Here we test that we can fetch a single car using its id
     */
    @Test
    public void testGetCarById() {
        Car car = restTemplate.getForObject(getRootUrl() + "/cars/1", Car.class);
        System.out.println(car.getCarName());
        Assert.assertNotNull(car);
    }

    /**
     * Here we test that we can create a car using the POST method
     */
    @Test
    public void testCreateCar() {
        Car car = new Car();
        car.setCarName("Prius");
        car.setDoors(4);

        ResponseEntity<Car> postResponse = restTemplate.postForEntity(getRootUrl() + "/cars", car, Car.class);
        Assert.assertNotNull(postResponse);
        Assert.assertNotNull(postResponse.getBody());
    }

    /**
     * Here we test that we can update a car's information using the PUT method
     */
    @Test
    public void testUpdateCar() {
        int id = 1;
        Car car = restTemplate.getForObject(getRootUrl() + "/cars/" + id, Car.class);
        car.setCarName("Tesla");
        car.setDoors(2);

        restTemplate.put(getRootUrl() + "/cars/" + id, car);

        Car updatedCar = restTemplate.getForObject(getRootUrl() + "/cars/" + id, Car.class);
        Assert.assertNotNull(updatedCar);
    }
Las pruebas simulan varias acciones que son posibles en nuestra API y esta es nuestra forma de verificar que la API funciona como se espera. Si se realizara un cambio mañana, las pruebas ayudarán a determinar si alguna de las funciones de la API está dañada y, al hacerlo, evitará que la funcionalidad se rompa al efectuar cambios.
Piense en las pruebas como en una lista de compras cuando vaya al supermercado. Sin él, podríamos terminar eligiendo casi todo lo que encontramos que creemos que podría ser útil. Nos puede llevar mucho tiempo conseguir todo lo que necesitamos. Si tuviéramos una lista de compras, podríamos comprar exactamente lo que necesitamos y terminar de comprar más rápido. Las pruebas hacen lo mismo para nuestras API, nos ayudan a definir el alcance de la API para que no implementemos una funcionalidad que no estaba en los planes o que no era necesaria.
Cuando ejecutamos nuestras pruebas usando el mvn testcomando, veremos que surgen errores y esto se debe a que aún no hemos implementado la funcionalidad que satisface nuestros casos de prueba.
En TDD, primero escribimos las pruebas, las ejecutamos para asegurarnos de que inicialmente fallan, luego implementamos la funcionalidad para hacer que las pruebas pasen.
TDD es un proceso iterativo de escritura de pruebas e implementación de la funcionalidad para hacer que las pruebas pasen. Si introducimos algún cambio en el futuro, primero escribiremos las pruebas y luego implementaremos los cambios para hacer que se aprueben las nuevas pruebas.

Controlador

Permítanos ahora implementar nuestra funcionalidad de API en una CarControllerque vaya a la controllerscarpeta:
@RestController
@RequestMapping("/api/v1")
public class CarController {

    @Autowired
    private CarRepository carRepository;

    // GET Method for reading operation
    @GetMapping("/cars")
    public List<Car> getAllCars() {
        return carRepository.findAll();
    }

    // GET Method for Read operation
    @GetMapping("/cars/{id}")
    public ResponseEntity<Car> getCarsById(@PathVariable(value = "id") Long carId)
        throws ResourceNotFoundException {

        Car car = carRepository
                  .findById(carId)
                  .orElseThrow(() -> new ResourceNotFoundException("Car not found on :: " + carId));
        return ResponseEntity.ok().body(car);
    }

    // POST Method for Create operation
    @PostMapping("/cars")
    public Car createCar(@Valid @RequestBody Car car) {
        return carRepository.save(car);
    }

    // PUT Method for Update operation
    @PutMapping("/cars/{id}")
    public ResponseEntity<Car> updateCar(
        @PathVariable(value = "id") Long carId, @Valid @RequestBody Car carDetails)
        throws ResourceNotFoundException {
            Car car = carRepository
                      .findById(carId)
                      .orElseThrow(() -> new ResourceNotFoundException("Car " + carId + " not found"));

        car.setCarName(carDetails.getCarName());
        car.setDoors(carDetails.getDoors());

        final Car updatedCar = carRepository.save(car);
        return ResponseEntity.ok(updatedCar);
    }

    // DELETE Method for Delete operation
    @DeleteMapping("/car/{id}")
    public Map<String, Boolean> deleteCar(@PathVariable(value = "id") Long carId) throws Exception {
        Car car = carRepository
                  .findById(carId)
                  .orElseThrow(() -> new ResourceNotFoundException("Car " + carId + " not found"));

        carRepository.delete(car);
        Map<String, Boolean> response = new HashMap<>();
        response.put("deleted", Boolean.TRUE);
        return response;
    }
}
En la parte superior, tenemos la @RestControlleranotación para definir nuestra CarControllerclase como el controlador para nuestra API Spring Boot. Lo que sigue es @RequestMappingdonde especificamos la ruta base de nuestra URL de API como /api/v1Esto también incluye la versión.
El control de versiones es una buena práctica en una API para mejorar la compatibilidad con versiones anteriores. Si la funcionalidad cambia y ya tenemos otras personas que consumen nuestras API, podemos crear una nueva versión y hacer que ambas se ejecuten simultáneamente para darles suficiente tiempo para migrar a la nueva API.
Anteriormente, aprendimos sobre las operaciones Crear, Leer, Actualizar y Eliminar en una API y cómo se asignan a los Métodos HTTP. Estos métodos se alojan en el marco del resorte como PostMappingGetMappingPutMappingDeleteMappinganotaciones, respectivamente. Cada una de estas anotaciones nos ayuda a exponer puntos finales que solo realizan la operación CRUD especificada.
También podemos tener un único punto final que maneja varios métodos HTTP:
@RequestMapping(value="/cars", method = { RequestMethod.GET, RequestMethod.POST })
Ahora que hemos implementado la funcionalidad, vamos a ejecutar nuestras pruebas:
Resultados de la prueba
Las pruebas aprobadas nos muestran que hemos implementado la funcionalidad deseada al escribir las pruebas y nuestra API funciona.
Permítanos interactuar con nuestra API a través de Postman , que es una herramienta que ayuda a interactuar con las API cuando las desarrolla o consume.
Comenzamos por buscar todos los autos que tenemos almacenados en nuestra base de datos:
Conseguir carros vacios
Al principio, no tenemos coches almacenados. Añadamos nuestro primer coche:
Publicar el primer coche
La respuesta es la idy los detalles del coche que acabamos de agregar. Si agregamos algunos autos más y recuperamos todos los autos que hemos guardado:
Conseguir todos los coches
Estos son los autos que hemos creado usando nuestra API Spring Boot. Una comprobación rápida en la base de datos devuelve la misma lista:
Base de datos de todos los coches

Swagger UI

Hemos desarrollado y probado nuestra API utilizando TDD y ahora, para mejorar nuestra API, la documentaremos mediante la interfaz de usuario Swagger , que nos permite crear una interfaz generada automáticamente para que otros usuarios puedan interactuar y aprender sobre nuestra API.
Primero, agreguemos las siguientes dependencias en nuestro pom.xml:
<dependency>  
  <groupId>io.springfox</groupId>
  <artifactId>springfox-swagger2</artifactId>
  <version>2.7.0</version>
</dependency>

<dependency>  
  <groupId>io.springfox</groupId>
  <artifactId>springfox-swagger-ui</artifactId>
  <version>2.7.0</version>
</dependency>  
A continuación, crearemos una SwaggerConfig.javaen la misma carpeta que CarsApplication.java, que es el punto de entrada a nuestra API.
El SwaggerConfig.javaarchivo permite también agregar alguna información sobre nuestra API:
@Configuration
@EnableSwagger2
public class SwaggerConfig {  
    @Bean
    public Docket api() {
        return new Docket(DocumentationType.SWAGGER_2)
            .select()
            .apis(RequestHandlerSelectors.basePackage("com.example.cars"))
            .paths(PathSelectors.any())
            .build()
            .apiInfo(metadata());
    }

    /**
     * Adds metadata to Swagger
     *
     * @return
     */
    private ApiInfo metadata() {
        return new ApiInfoBuilder()
            .title("Cars API")
            .description("An API to store car details built using Spring Boot")
            .build();
    }
}
Ahora anotamos nuestros puntos finales para que aparezcan en la interfaz de Swagger UI que se generará. Esto se consigue de la siguiente manera:
// Add this import in our controller file...
import io.swagger.annotations.ApiOperation;

// ...then annotate our HTTP Methods
@ApiOperation(value="Fetches all cars in the database", response=Car.class)
@PostMapping("/...") // Our endpoint
Hemos especificado nuestra clase de respuesta como la Carclase, ya que es la que se utilizará para completar los detalles de nuestras respuestas. Hemos hecho esto porque la interfaz de usuario de Swagger nos permite agregar información sobre las cargas útiles de solicitud y los detalles de respuesta. Esto ayudará a proporcionar más información sobre las cargas útiles, como el tipo de valores que requiere nuestra API y el tipo de respuesta que se devolverá. También podemos especificar campos obligatorios en la documentación.
En nuestro caso, también utilizaremos la Carclase para formatear y validar nuestros parámetros de solicitud. Por lo tanto, anotamos sus "captadores" de la siguiente manera:
    @ApiModelProperty(name="id",
                      value="The id of the car",
                      example="1")
    public long getId() {
        return id;
    }

    @ApiModelProperty(name="carName",
                      value="The name of the car to be saved",
                      example="Bugatti",
                      required=true)
    public String getCarName() {
        return carName;
    }

    @ApiModelProperty(name="doors",
                      value="The number of doors that the car has",
                      example="2",
                      required=true)
    public int getDoors() {
        return doors;
    }
¡Eso es! Nuestra documentación está lista. Cuando ejecutamos nuestra API usando mvn spring-boot:runy navegando a http://localhost:8080/swagger-ui.htmlpodemos ver la documentación de nuestra API:
Swagger UI final
Swagger UI ha documentado todos nuestros puntos finales e incluso ha proporcionado funcionalidad para interactuar con nuestra API directamente desde la documentación. Como se puede ver en la sección inferior derecha de la captura de pantalla, nuestros valores de ejemplo se han completado previamente para que podamos probar rápidamente la API sin tener que volver a escribir los valores.

Conclusión

Java es un lenguaje poderoso y hemos aprovechado su poder para construir una interfaz de programación de aplicaciones, o API, utilizando el marco Spring Boot. Hemos podido implementar cuatro de los Métodos HTTP para manejar las diferentes operaciones de Crear, Leer, Actualizar y Eliminar en los detalles de nuestros autos.
Swagger UI también nos ha permitido documentar nuestra API de manera sencilla pero detallada y tener esta documentación expuesta como un punto final en nuestro servicio. Habiendo notado las ventajas del desarrollo de Test-Driven, seguimos adelante, escribimos pruebas para nuestros puntos finales y nos aseguramos de que nuestra funcionalidad y pruebas estuvieran alineadas.
El código fuente de este proyecto está disponible aquí en Github .

Acerca de: Programator

Somos Instinto Programador

0 comentarios:

Publicar un comentario

Dejanos tu comentario para seguir mejorando!

Con tecnología de Blogger.