L’esecuzione dei test è parte integrante del processo di costruzione del software:
Gradle mappa con i task il processo di costruzione del software
L’esecuzione di un task implica l’esecuzione di tutti i precedenti!
test (implica compileTestJava che implica compileJava): esegue tutti i testcheck (implica test): esegue i test ed eventuali controlli aggiuntivi che vedremo in futuroassemble (implica jar che impica compileJava): costruisce gli artefatti “deliverable”build: esegue sia check che assembleUn buon modo per eseguire l’intero ciclo di vita è ./gradlew build javadoc!
Gradle separa i sorgenti di test da quelli principali del programma in modo netto:
src/mainsrc/testQuesta è un’ottima pratica: quando andiamo a costruire il nostro “deliverable”, non vogliamo portarci dietro tutti i test compilati, né tantomeno le librerie che usiamo solo per i test!
D’altra parte, vogliamo che i sorgenti di test siano in effetti parte del progetto.
Il plugin java, di per sé, non configura nessuna suite di test specifica:
va aggiunta al build file la configurazione di JUnit,
seguendo questi passi:
Esistono diversi repository con librerie, quello di riferimento per Java è Maven Central
repositories { // Where to search for dependencies
mavenCentral()
}
Dobbiamo specificare che sono dipendenze, che ci servono solo per i test, che il motore di esecuzione serve solo a runtime, e che vogliamo una specifica versione
dependencies {
// JUnit API and testing engine
testImplementation(platform("org.junit:junit-bom:6.0.1"))
testImplementation("org.junit.jupiter:junit-jupiter-api")
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine")
}
test generato dal plugin java ha un comodo metodo useJunitPlatform() che pre-configura JUnit 5/6.tasks.withType<Test>().configureEach {
useJUnitPlatform() // Enables the engine of JUnit 5/6
testLogging { // Additional Options
// Display all events (test started, succeeded, failed...)
events(*org.gradle.api.tasks.testing.logging.TestLogEvent.entries.toTypedArray())
showStandardStreams = true // Show the standard output
}
}
Da adesso Gradle può lanciare i test JUnit 5/6 tramite il task test!
Finora, abbiamo visto che un’applicazione Java è composta di un insieme di classi: noi vorremmo distribuirla come singolo file!
Normalmente, le applicazioni Java vengono confezionate in file JAR (Java ARchive)
Un JAR è un archivio ZIP che contiene le classi, le risorse (e.g. icone) e un file descrittivo detto Manifest.
Il Manifest viene creato sempre in META-INF/MANIFEST.MF, e
contiene informazioni sull’applicazione, ad esempio su quale classe
contenga il main del programma.
È possibile associare (a livello di sistema operativo) l’esecuzione del JAR file al comando Java, in modo che l’applicazione si avvii automaticamente “col doppio click” (avviando automaticamente la classe scritta nel Manifest)
È possibile utilizzare i file JAR come componenti di altre applicazioni (librerie)
jarPer creare un file JAR, si può utilizzare direttamente il comando jar
c — Specifica l’intenzione di creare un JAR filef — Specifica un file di output (se non presente, l’output è rediretto su
standard output)m — Specifica l’intenzione di allegare un manifest file personalizzato (se non
presente, ne viene creato uno di default, che non specifica alcuna classe da
eseguire)jar cf jar-file.jar file1 file2 directory1
jar-file.jar contenente file1,
file2, e directory1. Include un Manifest di default.jar cf jar-file.jar *
jar-file.jar contenente tutti i file
e le directory nel path corrente. Include un Manifest di default.jar cfm jar-file.jar MYMANIFEST it/unibo/*
jar-file.jar, contenente tutti i
file e le directory nel path it/unibo, con manifest dato dal file
MYMANIFEST nella cartella corrente.java ha un’opzione che consente l’esecuzione di file jar: -jar
Quando si lancia java -jar nomefile.jar,
la Java Virtual Machine automaticamente legge il file Manifest,
cerca una descrizione della Main Class da eseguire e tenta di eseguirla.
Il plugin java include un task chiamato jar che si occupa di creare un jar dell’applicazione corrente
*.class) e le risorse del source set main
src/mainPerché un Jar sia eseguibile, deve includere:
src/mainNoClassDefFoundError o delle ClassNotFoundException (in reflection)Un Jar file con queste caratteristiche viene definito anche fat-jar (o uber-jar, o shadow-jar)
Il plugin java non ha dei task preconfigurati per generare dei fat-jar.
Possiamo però sfruttare una coppia di altri plugin:
application: un plugin incluso in Gradle che configura una classe come “main class” per il software
run che compila il progetto e la eseguecom.gradleup.shadow: un plugin di terze parti
(ossia, costruito da una persona che non fa parte del team di Gradle)
shadowJar, che costruisce un jar con tutte le dipendenze specificate in Gradleapplication, cattura anche la main class e scrive il relativo fileNota: è sempre possibile costruire i propri task personalizzati, ma non è argomento di questo corso
application e com.gradleup.shadowplugins {
// Apply the java plugin to add support for Java
java
// Apply the application plugin to add support for building a CLI application
// You can run your app via task "run": ./gradlew run
application
/*
* Adds tasks to export a runnable jar.
* In order to create it, launch the "shadowJar" task.
* The runnable jar will be found in build/libs/projectname-all.jar
*/
id("com.gradleup.shadow") version "9.2.2"
}
application {
// Define the main class for the application.
mainClass.set("it.unibo.sampleapp.RateAMovie")
}
./gradlew run lancia your.mainclass.qualified.Name./gradlew shadowJar crea un fat-jar il cui nome termina in -all.jar dentro build/libsapplication con multipli main tramite proprietà di progettoNel caso in cui vi fossero più classi con un main, è possibile passare a Gradle il nome di classe da lanciare.
Per farlo, sfruttiamo il concetto di proprietà del progetto.
Le proprietà sono associazioni chiave-valore fra due stringhe che possono essere:
gradle.properties $\leftarrow$ per OOP cambia poco rispetto a scriverlo nel build.gradle.kts-Pnome=valore $\leftarrow$ utile se si hanno più mainScrivendo quanto segue nel build.gradle.kts
val myMainClass: String by project // prende la main class dalle proprietà di progetto
application {
mainClass.set(myMainClass)
}
È possibile poi scegliere la main class da avviare usando:
./gradlew run -PmyMainClass=...
... il nome qualificato della main class)Ad esempio, se abbiamo due classi con main it.unibo.oop.Pluto e it.unibo.oop.Paperino nel nostro progetto, possiamo lanciare:
./gradlew run -PmyMainClass=it.unibo.oop.Pluto
Pluto./gradlew run -PmyMainClass=it.unibo.oop.Paperino
PaperinoLa documentazione di un progetto software è un aspetto fondamentale Al fine di garantirne la manutenibilità
doStuff() sviluppato dal collega?javadocStrumento di supporto per la generazione automatica di documentazione HTML di software Java tramite utilizzo di una specifica sintassi nei commenti
/** ... */import java.util.List;
/**
* This is an exemplary documentation of a class named {@code Something}.
* It is possible to use <i>HTML tags</i> in this text, and special characters such as & must be
* escaped as uyou would escape them in HTML.
* Code can be written in {@code code tags}. Other entities can be linked via {@code @link}, e.g.,
* the {@link List} interface, or the {@link List#of(Object...)} method.
*
* @param <T> This is the documentation specific for the class parameter {@code T}
* @see List#size()
* @deprecated This contains information on the reason why this class should not be used in new code,
* when (if) will eventually be removed, and what to use as a replacement.
*/
@Deprecated
interface Something<T> {
/**
* This is the documentation of a method. The text is still HTML.
*
* @param subject Documents the parameter @{code subject}
* @param count Documents the parameter @{code count} (one entry per parameter)
* @return Provides information on the returned information
* @throws IOException Provides detail on the causes that may trigger an @{code IOException}
* @throws IllegalArgumentException If more exceptions are thrown, each should get documented
*
*/
@Deprecated
int doIt(List<T> subject, int count) throws IOException { ... }
}
@param
@return
@throws
@see
List e SetLa deprecazione è il processo di demarcazione di codice esistente come non più utile nella codebase, tipicamente perché rimpiazzato da nuovi componenti con un design rivisto e migliorato.
Questo codice viene solo marchiato come deprecato e non rimosso immediatamente per evitare problemi di retrocompatibilità.
Ciononostante, il suo utilizzo in nuovo codice è fortemente scoraggiato, mentre il passaggio al codice sostitutivo è fortemente raccomandato.
In Java, le entità deprecate vanno annotate con @Deprecated,
e opportunamente documentate col tag @deprecated
@DeprecatedQuesti tag servono a migliorare la formattazione del testo, senza dover passare per tag HTML. Tipicamente appaiono nel testo principale.
{@link target}
{@link qualified.name.of.OtherClass#someMethod}{@link #someMethodOfSameClass}{@link #someFieldOfSameClass}{@code testo}
testo con un font monospaziato{@inheritDoc}
@Override)Esistono alcuni tag aggiuntivi il cui utilizzo è scoraggiato (almeno in questo corso). Questo, tipicamente, perché indicano informazioni che sono reperibili a partire dal sistema di controllo versione, ma che, a differenza delle informazioni tracciate da quest’ultimo, sono manuali
@since
@author
@version
public e protectedprivate (non sono comunque chiamabili dal codice cliente){@inheritDoc}.Il tool javadoc è molto potente e ricco di opzioni,
usarlo direttamente è un esercizio non banale
che va oltre lo scopo di questo corso.
È possibile però appoggiarsi a Gradle per generare documentazione con impostazioni di default ragionevoli.
Il plugin java di Gradle aggiunge un task javadoc che documenta automaticamente tutto il sorgente in src/main/java,
generando il sito web relativo in build/
NOTA – In caso di javadoc incompleta, il task fallisce!