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 assemble
Un 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/main
src/test
Questa è 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 {
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 {
val junitVersion = "5.9.1"
testImplementation("org.junit.jupiter:junit-jupiter-api:$junitVersion")
testImplementation("org.junit.jupiter:junit-jupiter-params:$junitVersion")
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:$junitVersion")
}
Il task test
generato dal plugin java
ha un comodo metodo che pre-configura JUnit 5.
tasks.test {
useJUnitPlatform()
}
tasks.test {
testLogging { events(TestLogEvent.values()) }
testLogging.showStandardStreams = true
}
plugins {
java
}
repositories {
mavenCentral()
}
dependencies {
val junitVersion = "5.9.1"
testImplementation("org.junit.jupiter:junit-jupiter-api:$junitVersion")
testImplementation("org.junit.jupiter:junit-jupiter-params:$junitVersion")
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:$junitVersion")
}
tasks.test {
useJUnitPlatform()
testLogging { events(TestLogEvent.values()) }
testLogging.showStandardStreams = true
}
Da adesso Gradle può lanciare i test JUnit 5 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)
jar
Per 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/main
Perché un Jar sia eseguibile, deve includere:
src/main
NoClassDefFoundError
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.github.johnrengelman.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.github.johnrengelman.shadow
plugins {
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.
* Always pick the latest version!
* In order to create it, launch the "shadowJar" task.
* The runnable jar will be found in build/libs/projectname-all.jar
*/
id("com.github.johnrengelman.shadow") version "7.0.0" // Update the version!
}
application {
// Define the main class for the application
mainClass.set("your.mainclass.qualified.Name")
}
./gradlew run
lancia your.mainclass.qualified.Name
./gradlew shadowJar
crea un fat-jar il cui nome termina in -all.jar
dentro build/libs
application
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
Paperino
La documentazione di un progetto software è un aspetto fondamentale Al fine di garantirne la manutenibilità
doStuff()
sviluppato dal collega?javadoc
Strumento 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 Set
La 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
@Deprecated
Questi 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 protected
private
(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!