ANDERSSON DAVID SÁNCHEZ MÉNDEZ
CRISTIAN SANTIAGO PEDRAZA RODRÍGUEZ
Java OpenJDK Runtime Environment 17.x.x
Apache Maven: 3.9.x
JUnit: 5.x.x
Docker
- ¿Cómo hacer pruebas unitarias?
- Utilizar anotaciones @Test del framework JUnit.
- Aplicar TDD.
El proyecto consiste en un sistema de gestión de bibliotecas, donde hay clases que representan Libro, Usuario, Prestamo, y Biblioteca. Los usuarios pueden tomar prestados libros de la biblioteca, y la Biblioteca se encarga de gestionar los préstamos, asegurarse de que los libros estén disponibles, y mantener un registro de los libros prestados.
- Verificamos la versión
- Ahora creamos el directorio con el comando proporcionado.
Busque cómo ejecutar desde línea de comandos el objetivo "generate" del plugin "archetype", con los siguientes parámetros:
Grupo: edu.eci.cvds Artefacto: Library Paquete: edu.eci.cvds.tdd archetypeArtifactId: maven-archetype-quickstart Según documentación, hay que seguir este ejemplo
mvn archetype:generate -DgroupId=com.mycompany.app -DartifactId=my-app -DarchetypeArtifactId=maven-archetype-quickstart -DarchetypeVersion=1.5 -DinteractiveMode=falseHaciendo que nuestro comando quede como:
mvn archetype:generate -DgroupId=edu.eci.cvds -DartifactId=Library -DarchetypeArtifactId=maven-archetype-quickstart -DarchetypeVersion=1.5 -DinteractiveMode=false -Dpackage=edu.eci.cvds.tdd- Buscar en maven central la dependencia de JUnit5 en su versión más reciente.
- Edite el archivo pom.xml del proyecto para agregar la dependencia.
- Verifique que la versión de java sea la 17
<properties>
<maven.compiler.target>1.17</maven.compiler.target>
<maven.compiler.source>1.17</maven.compiler.source>
</properties>- Compile el proyecto para validar que todo este bien.
Cree los siguientes paquetes dentro de edu.eci.cvds.tdd
- library : book, loan, user
Estos paquetes también se deben crear en la carpeta de test.
En el paquete edu.eci.cvds.tdd.library.book cree la siguiente clase:
packageedu.eci.cvds.tdd.library.book;
publicclassBook {
privatefinalStringtittle;
privatefinalStringauthor;
privatefinalStringisbn;
publicBook(Stringtittle, Stringauthor, Stringisbn) {
this.tittle = tittle;
this.author = author;
this.isbn = isbn;
}
publicStringgetTittle() {
returntittle;
}
publicStringgetAuthor() {
returnauthor;
}
publicStringgetIsbn() {
returnisbn;
}
@Overridepublicbooleanequals(Objectobj) {
returnisbn.equals(((Book)obj).isbn);
}
}A continuación en el paquete edu.eci.cvds.tdd.library.user cree la siguiente clase:
packageedu.eci.cvds.tdd.library.user;
publicclassUser {
privateStringname;
privateStringid;
publicStringgetName() {
returnname;
}
publicvoidsetName(Stringname) {
this.name = name;
}
publicStringgetId() {
returnid;
}
publicvoidsetId(Stringid) {
this.id = id;
}
}En el paquete edu.eci.cvds.tdd.library.loan se deben crear las clases Loan y el enum LoanStatus:
packageedu.eci.cvds.tdd.library.loan;
importedu.eci.cvds.tdd.library.book.Book;
importedu.eci.cvds.tdd.library.user.User;
importjava.time.LocalDateTime;
publicclassLoan {
privateBookbook;
privateUseruser;
privateLocalDateTimeloanDate;
privateLoanStatusstatus;
privateLocalDateTimereturnDate;
publicBookgetBook() {
returnbook;
}
publicvoidsetBook(Bookbook) {
this.book = book;
}
publicUsergetUser() {
returnuser;
}
publicvoidsetUser(Useruser) {
this.user = user;
}
publicLocalDateTimegetLoanDate() {
returnloanDate;
}
publicvoidsetLoanDate(LocalDateTimeloanDate) {
this.loanDate = loanDate;
}
publicLoanStatusgetStatus() {
returnstatus;
}
publicvoidsetStatus(LoanStatusstatus) {
this.status = status;
}
publicLocalDateTimegetReturnDate() {
returnreturnDate;
}
publicvoidsetReturnDate(LocalDateTimereturnDate) {
this.returnDate = returnDate;
}
}packageedu.eci.cvds.tdd.library.loan;
publicenumLoanStatus {
ACTIVE, RETURNED
}Por último se debe crear la siguiente clase en el paquete edu.eci.cvds.tdd.library
packageedu.eci.cvds.tdd.library;
importedu.eci.cvds.tdd.library.book.Book;
importedu.eci.cvds.tdd.library.loan.Loan;
importedu.eci.cvds.tdd.library.user.User;
importjava.util.ArrayList;
importjava.util.HashMap;
importjava.util.List;
importjava.util.Map;
/** * Library responsible for manage the loans and the users. */publicclassLibrary {
privatefinalList<User> users;
privatefinalMap<Book, Integer> books;
privatefinalList<Loan> loans;
publicLibrary() {
users = newArrayList<>();
books = newHashMap<>();
loans = newArrayList<>();
}
/** * Adds a new {@link edu.eci.cvds.tdd.library.book.Book} into the system, the book is store in a Map that contains * the {@link edu.eci.cvds.tdd.library.book.Book} and the amount of books available, if the book already exist the * amount should increase by 1 and if the book is new the amount should be 1, this method returns true if the * operation is successful false otherwise. * * @param book The book to store in the map. * * @return true if the book was stored false otherwise. */publicbooleanaddBook(Bookbook) {
//TODO Implement the logic to add a new book into the map.returnfalse;
}
/** * This method creates a new loan with for the User identify by the userId and the book identify by the isbn, * the loan should be store in the list of loans, to successfully create a loan is required to validate that the * book is available, that the user exist and the same user could not have a loan for the same book * {@link edu.eci.cvds.tdd.library.loan.LoanStatus#ACTIVE}, once these requirements are meet the amount of books is * decreased and the loan should be created with {@link edu.eci.cvds.tdd.library.loan.LoanStatus#ACTIVE} status and * the loan date should be the current date. * * @param userId id of the user. * @param isbn book identification. * * @return The new created loan. */publicLoanloanABook(StringuserId, Stringisbn) {
//TODO Implement the login of loan a book to a user based on the UserId and the isbn.returnnull;
}
/** * This method return a loan, meaning that the amount of books should be increased by 1, the status of the Loan * in the loan list should be {@link edu.eci.cvds.tdd.library.loan.LoanStatus#RETURNED} and the loan return * date should be the current date, validate that the loan exist. * * @param loan loan to return. * * @return the loan with the RETURNED status. */publicLoanreturnLoan(Loanloan) {
//TODO Implement the login of loan a book to a user based on the UserId and the isbn.returnnull;
}
publicbooleanaddUser(Useruser) {
returnusers.add(user);
}
}Para validar que la estructura del proyecto está bien se debe compilar usando el comando package.
Para poder implementar los métodos addBook, loanABook y returnLoan de la clase Library vamos a aplicar la técnica de TDD, por cada caso de prueba se debe hacer un commit, cada commit debe tener la prueba nueva y la implementación para que la prueba del commit funcione. Las pruebas anteriormente implementadas deben continuar funcionando. Como están trabajando en parejas es necesario trabajar en ramas independientes y utilizar Pull Request para mezclar los cambios.
Teniendo en cuenta esa técnica, hicimos las pruebas primero, y nos damos cuenta que las pruebas no pasan porque no se ha hecho la implementación correspondiente:
Es necesario crear la clase de prueba para edu.eci.cvds.tdd.Library, la clase debe seguir los estándares de nombres estudiados en clase.
Para pensar en los casos de pruebas lean detenidamente el javadoc de los métodos para reconocer las clases de equivalencia, basados en las clases de equivalencia se debe crear una prueba la cual debe fallar y posteriormente implementar el código necesario para que funcione, este proceso se debe repetir hasta cumplir con la especificación definida en el javadoc.
- Agregar la dependencia de jacoco, utilizar la última versión disponible en maven central.
- Para usar Jacoco es necesario agregar la siguiente sección en el
pom.xml
<build>
<plugins>
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.12</version>
<executions>
<execution>
<goals>
<goal>prepare-agent</goal>
</goals>
</execution>
<execution>
<id>report</id>
<phase>test</phase>
<goals>
<goal>report</goal>
</goals>
<configuration>
<excludes>
<exclude>/configurators/</exclude>
</excludes>
</configuration>
</execution>
<execution>
<id>jacoco-check</id>
<goals>
<goal>check</goal>
</goals>
<configuration>
<rules>
<rule>
<element>PACKAGE</element>
<limits>
<limit>
<counter>CLASS</counter>
<value>COVEREDRATIO</value>
<minimum>0.85</minimum><!--Porcentaje mínimo de cubrimiento para construir el proyecto-->
</limit>
</limits>
</rule>
</rules>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>Ahora al compilar el proyecto en la carpeta target se debe crear una carpeta con el nombre site la cual tiene un index.html , al abrir dicho archivo se debe ver la cobertura total y de cada una de las clases, el objetivo es tener la cobertura superior al 80%.
Explore los links del reporte en el cual le muestra que partes del código tienen prueba y cuales no.
Ahora es necesario hacer el análisis estático del código usando SonarQube, para lo cual necesitamos tener Docker.
Para lo cual se debe descargar la assets/imagen de docker con el siguiente comando
docker pull sonarqube
Ahora se debe arrancar el servicio de SonarQube con el siguiente comando
docker run -d --name sonarqube -e SONAR_ES_BOOTSTRAP_CHECKS_DISABLE=true -p 9000:9000 sonarqube:latest
- Validar funcionamiento
docker ps -a
- Iniciar sesión en sonar
localhost:9000cambiar la clave por defecto usuario y contraseña es admin.
- Entrar a las opciones de la cuenta -> Account -> settings -> generate token; una vez sonar este corriendo deben generar un token.
- Instale sonarLint en el IDE que este manejando.
- Añada el plugin de Sonar en el archivo pom del proyecto.
<plugin>
<groupId>org.sonarsource.scanner.maven</groupId>
<artifactId>sonar-maven-plugin</artifactId>
<version>4.0.0.4121</version>
</plugin>- Añada las propiedades de SonarQube y Jacoco.
<sonar.projectKey>library</sonar.projectKey>
<sonar.projectName>library</sonar.projectName> <sonar.host.url>http://localhost:9000</sonar.host.url>
<sonar.coverage.jacoco.xmlReportPaths>target/site/jacoco/jacoco.xml</sonar.coverage.jacoco.xmlReportPaths>
<sonar.coverage.exclusions>src//configurators/*</sonar.coverage.exclusions>- Construya el proyecto, genere el reporte de JACOCO y corrija el cubrimiento de las pruebas de unidad para que su proyecto se construya adecuadamente.
- Genere la integración con sonar
mvn verify sonar:sonar -D sonar.token=[TOKEN_GENERADO]
En este caso con este comando: mvn verify sonar:sonar -D sonar.token=squ_ac755884f726e68a70bb732ded2f6c5d90c9622c
Luego de hacer todas las pruebas, y luego si hacer la implementación necesaria para que estas pasaran. Se obtuvo en SonarQube una cobertura de:
Y también se mira el análisis estático de las pruebas, validando que para clase se hicieron las pruebas correspondientes, y se cumple con las condiciones prestablecidas:
En este caso, se ignoran los directorios target y .mvn.




























