On ne présente plus le projet JBang et on a d’ailleurs vu ici comme démarrer un projet rapidement avec JBang.
JBang
permet de commencer à écrire un programme Java très rapidement et ce sans se soucier de Maven
, Gradle
ni même d’IDE ; il est donc particulièrement recommandé pour les débutants en Java.
1. Un script JBang
Un script JBang n’est rien d’autre qu’une classe Java
déclarée dans le package par défaut et qui a une visibilité soit publique soit défaut (package local).
Cette classe comporte:
- une méthode
main
à la visibilité publique - des méthodes à la visibilité publique ou défaut
- des méthodes privées (éventuellement)
2. Tester ce script JBang
Ce qu’on aimerait bien faire ici, c’est écrire une classe de test pour chaque script JBang, afin de garantir le bon fonctionnement de celui-ci et pourquoi pas écrire nos scripts JBang en faisant du TDD (Test Driven Development).
Pour cela, il faut s’intéresser aux méthodes que l’on va tester.
La méthode main
retourne uniquement un code retour, va écrire sur la sortie standard, et peut-être interagir avec le système de fichiers: ce n’est probablement pas elle que l’on va tester.
Les méthodes Java de notre script prennent plusieurs paramètres en entrée et retournent un objet en sortie.
Elles sont publiques ou ont la visibilité défaut: elles sont donc accessibles depuis le package par défaut et il nous sera facile de tester ce qu’elles retournent en fonction des entrées qu’on va leur fournir.
On peut donc écrire un deuxième script JBang que l’on nommera par exemple ScriptTest.java
, et qui sera le script de test de Script.java
.
2.1. Note sur les méthodes privées
Vous remarquerez que si le script peut dans l’absolu faire usage de méthodes privées, cela n’apporte pas grand chose dans un script JBang.
En revanche, faire le choix de les rendre toutes publique ou avec la visibilité défaut présente l’avantage non négligeable de pouvoir toutes les tester !
3. Un script de test JBang
On va écrire un script JBang très simple qui affiche la liste des n premiers éléments de la suite de Fibonacci (en utilisant l’algorithme le plus simple possible, la performance n’étant pas notre problème ici):
$ cat Fibonacci.java
///usr/bin/env jbang "$0" "$@" ; exit $?
//DEPS info.picocli:picocli:4.6.2
import picocli.CommandLine;
import picocli.CommandLine.Command;
import picocli.CommandLine.Parameters;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
@Command(name = "Fibonacci", mixinStandardHelpOptions = true, version = "Fibonacci 0.1", description = "Fibonacci made with jbang")
public class Fibonacci implements Callable<Integer> {
@Parameters(index = "0", description = "Number of elements to compute")
private int max;
public static void main(String... args) {
int exitCode = new CommandLine(new Fibonacci()).execute(args);
System.exit(exitCode);
}
@Override
public Integer call() {
List<Integer> suites = getFibonacciList(max);
System.out.println(suites);
return 0;
}
public List<Integer> getFibonacciList(int max) {
List<Integer> suites = new ArrayList<>();
for (int n = 0; n < max; n++) {
suites.add(getFibonacci(n));
}
return suites;
}
public Integer getFibonacci(int n) {
if (n == 0) {
return 0;
}
if (n == 1) {
return 1;
}
return getFibonacci(n - 1) + getFibonacci(n - 2);
}
}
Et pour obtenir un script de test qui lui correspond, on va utiliser le template junit
du catalogue JBang:
$ jbang init --template=junit@jbangdev Fibonacci.java
[jbang] File initialized. You can now run it with 'jbang FibonacciTest.java' or edit it using 'jbang edit --open=[editor] FibonacciTest.java' where [editor] is your editor or IDE, e.g. 'netbeans'
Ce template permet de générer un script de test utilisant JUnit 5.
On obtient alors le script de test FibonacciTest.java
:
$ cat FibonacciTest.java
///usr/bin/env jbang "$0" "$@" ; exit $?
//DEPS org.junit.jupiter:junit-jupiter-api:5.8.2
//DEPS org.junit.jupiter:junit-jupiter-engine:5.8.2
//DEPS org.junit.platform:junit-platform-launcher:1.8.2
//SOURCES Fibonacci.java
import org.junit.jupiter.api.Test;
import org.junit.platform.launcher.Launcher;
import org.junit.platform.launcher.LauncherDiscoveryRequest;
import org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder;
import org.junit.platform.launcher.core.LauncherFactory;
import org.junit.platform.launcher.listeners.SummaryGeneratingListener;
import org.junit.platform.launcher.listeners.LoggingListener;
import static java.lang.System.out;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass;
// JUnit5 Test class for Fibonacci
public class FibonacciTest {
// Define each Unit test here and run them separately in the IDE
@Test
public void testFibonacci() {
assertEquals(1,2, "You should add some testing code for Fibonacci here!");
}
// Run all Unit tests with JBang with ./FibonacciTest.java
public static void main(final String... args) {
final LauncherDiscoveryRequest request =
LauncherDiscoveryRequestBuilder.request()
.selectors(selectClass(FibonacciTest.class))
.build();
final Launcher launcher = LauncherFactory.create();
final LoggingListener logListener = LoggingListener.forBiConsumer((t,m) -> {
System.out.println(m.get());
if(t!=null) {
t.printStackTrace();
};
});
final SummaryGeneratingListener execListener = new SummaryGeneratingListener();
launcher.registerTestExecutionListeners(execListener, logListener);
launcher.execute(request);
execListener.getSummary().printTo(new java.io.PrintWriter(out));
}
}
Vous noterez au début du script de test la ligne:
//SOURCES Fibonacci.java
qui indique que le script de test dépend du script de production correspondant.
On distingue ensuite deux parties dans ce script de test:
- Un premier test
testFibonacci
qui échoue – c’est fait exprès afin que l’utilisateur le modifie et teste ce qui fait sens pour lui - Une méthode
main
qui lançera ce premier test ainsi que tous ceux que vous aurez ajouté par la suite et affichera ensuite un résumé de l’exécution des tests
La méhode main
sera lançée par JBang à l’éxécution de FibonacciTest.java
.
4. Edition du script JBang de test
Au delà d’écrire un script de test, ce qui nous intéresse ici est de pouvoir faire du TDD avec notre script JBang, ce qui nécessite de pouvoir éditer à la fois script de test et script de production.
C’est possible très facilement avec JBang:
$ jbang edit FibonacciTest.java
[jbang] Running `sh -c idea /home/pyfourmond/.jbang/cache/projects/FibonacciTest.java_jbang_3d132c852ffb9462f5add1b68d3efa9e62dc6b3f0da7d2c1f60b0de69c5c5e22/FibonacciTest`
/home/pyfourmond/.jbang/cache/projects/FibonacciTest.java_jbang_3d132c852ffb9462f5add1b68d3efa9e62dc6b3f0da7d2c1f60b0de69c5c5e22/FibonacciTest
Comme j’ai défini la variable d’environnement suivante:
JBANG_EDITOR=idea
C’est IntelliJ IDEA qui se lance:
Et comme vous pouvez le voir, JBang nous permet d’utiliser IDEA pour éditer code de production ET code de test, et ce sans avoir à éditer le moindre fichier pom.xml
ou build.gradle
!
C’est la commande jbang edit
qui a fait tout le travail pour nous !
Ici, j’ai ajouté 3 tests pour valider mon implémentation.
On peut évidemment lancer chacun de ces tests unitairement dans IDEA ainsi que l’ensemble des tests comme on a l’habitude de le faire avec IntelliJ IDEA.
5. Un répertoire de tests
JBang permet de faire l’économie de la sacro-sainte arborescence Java src/main/java
et src/test/java
.
Le répertoire src
que l’on voit ici dans IntelliJ IDEA est généré par jbang edit
et n’existe donc que dans le projet temporaire généré par cette dernière commande.
Tout cela pour dire que nos deux scripts Fibonacci.java
et FibonacciTest.java
sont situés à la racine du dépôt Git qui les contient.
Néanmoins, si notre dépôt Git contient plusieurs scripts JBang, on peut trouver cela plus propre de placer nos scripts de tests dans leur propre répertoire, par exemple tests
.
On pourra alors procéder comme suit:
$ mkdir -p tests
$ jbang init -DscriptName=Fibonacci --template=junit5@grumpyf0x48 tests/FibonacciTest.java
[jbang] File initialized. You can now run it with 'jbang tests/FibonacciTest.java' or edit it using 'jbang edit --open=[editor] tests/FibonacciTest.java' where [editor] is your editor or IDE, e.g. 'idea'
Et il faudra ensuite modifier la directive SOURCES
qui ne fait pas référence au bon chemin et qu’il faudra modifier en:
//SOURCES ../Fibonacci.java
Pour pouvoir lancer le test avec:
$ ./tests/FibonacciTest.java
Vous noterez qu’ici j’ai utilisé mon template JUnit de test JBang au lieu de celui du catalogue JBang pour lequel la commande aurait été:
$ jbang init --template=junit@jbangdev tests/Fibonacci.java
puisque dans ce cas c’est le template qui ajoute Test
à la fin du nom du fichier.
6. Conclusion
On a vu dans ce billet que le fait d’écrire des scripts JBang ne nous dispensait pas d’écrire des tests unitaires !
L’utilisation d’un template pour les tests JUnit permet en une commande jbang
de disposer d’un script de test qui s’éxécute.
Les tests peuvent être lancés depuis l’IDE comme on a l’habitude de le faire mais aussi en ligne de commande depuis le terminal.
Pour aller plus loin, on pourra exécuter les tests depuis un workflow GitHub, par exemple en utilisant la GitHub action de JBang, ou utiliser le nouveau plugin JBang pour IntelliJ IDEA.
Une autre piste intéressante à explorer serait de regrouper tous nos tests dans une « test suite ».
Note: Les sources utilisées dans ce billet sont disponibles.