Démarrer avec Kotlin

J’ai remarqué que peu de développeurs savaient bootstrap un nouveau projet. La preuve en est pour Kotlin, où j’ai dû demander à plusieurs développeurs avant d’obtenir l’aide de Cédric pour régler une broutille. Vu que j’aime faire des petits ateliers de code et également en animer, des katas par exemple, je me suis dit que ce serait pratique d’avoir un guide de démarrage rapide, avec l’essentiel pour m’en rappeler ou pour le donner aux participants d’un atelier. Des choses comme la création du projet, l’ajout de dépendances, les différents tests, et quelques liens utiles.

Voici donc un premier guide pour Kotlin. Je le partage pour m’aider moi ou quiconque voudra se lancer rapidement dans un petit projet de code avec des tests. C’est un essentiel à l’instant T, je le mettrais possiblement à jour dans le temps avec de nouvelles choses. Si vous avez des remarques, je suis preneur.

Création d’un projet

Il existe d’autres solutions, mais je ne parlerais que de Gradle qui est plus récent et qui est recommandé.

Via Intellij IDEA

JetBrains a créé le langage et l’outillage. La création d’un projet Kotlin est simple et rapide. On passe par le menu : File > New > Project.

Les possibilités changent ensuite selon l’édition de Intellij et/ou du système d’exploitation je suppose.

Si vous avez la possibilité, sélectionnez Kotlin puis Console Application. Gradle y sera ajouté par défaut. La documentation officielle va dans ce sens. Sinon, si vous êtes comme moi sur macOS avec l’édition Intellij IDEA CE, sélectionnez Gradle puis cochez Kotlin/JVM.

Plus d’informations : https://kotlinlang.org/docs/jvm-get-started.html

En ligne de commande

La simplicité est également présente en ligne de commande. Il suffit d’exécuter gradle init puis de se laisser guider. Ce que je choisis en général :

  • Type of project : application.
  • Implementation language : Kotlin.
  • Script DSL : Groovy ou Kotlin.

Exécution du projet et des tests

Via Intellij IDEA

D’un clic-droit sur un module ou un fichier dans l’arborescence, il est possible de compiler le projet, lancer le projet, et lancer les tests.

Les résultats des tests sont visibles dans un des onglets du bas d’Intellij IDEA. Il est possible de voir tous les tests, ou de filtrer selon le succès ou l’échec.

⚠️ Kotest : Parfois, il faut aller sur le menu latéral « Gradle » et cliquer sur Tasks > verification > test. Lancer la tâche « test » manuellement pour voir tous les tests se lancer. Problème dû à kotest ou à la présence de plusieurs frameworks de tests ?

En ligne de commande

Le projet étant créé avec Gradle, on passera toujours par la commande gradle pour effectuer des actions.

  • Lancer les tests : gradle test
  • Lancer les tests automatiquement (watch mode) : gradle test —continuous
  • Lancer l’application : gradle run

Choix de l’IDE

Intellij IDEA

Sans hésiter, je dirais d’utiliser Intellij IDEA. JetBrains a un très bon savoir-faire. L’IDE est très complet en termes de fonctionnalités, les possibilités de refactoring sont nombreuses, et l’écosystème Kotlin est très bien intégré. La version communautaire est en plus gratuite.

On trouve toutes les options de refactoring à l’aide du raccourci Ctrl + T.

Gestion des dépendances

Le fichier de configuration principal est build.gradle (Groovy) ou build.gradle.kts (Kotlin).

Pour ajouter des dépendances, on est souvent amené à insérer des lignes dans l’étape nommée dependencies. Par exemple :

dependencies {
    testImplementation 'org.jetbrains.kotlin:kotlin-test-junit5'
    testImplementation 'org.junit.jupiter:junit-jupiter-api:5.6.0'
    testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.6.0'
}

⚠️ Lorsque l’on ajoute une nouvelle dépendance, il faut vérifier que celle-ci ne va pas entrer en conflit ou n’est pas en double (sous une autre version). Ce peut être le cas lorsque l’on initialise le projet en ligne de commande et que l’on ajoute les dépendances de JUnit. L’erreur est difficilement visible : au lancement des tests, aucun d’eux n’est trouvé.

⚠️ Il existe plusieurs sources pour récupérer les dépendances : les repositories. Il peut être nécessaire de changer ou d’ajouter une source pour résoudre les dépendances.

⚠️ Après un changement dans le fichier de configuration, il peut être nécessaire de recharger Gradle dans Intellij IDEA. Une petite icône Gradle est peut-être apparue par-dessus le contenu du fichier.

Variables

On peut définir des variables dans le fichier de configuration. Exemple en Groovy :

def kotestversion = '4.4.3'

dependencies {
    testImplementation 'org.jetbrains.kotlin:kotlin-test-junit5'
    testImplementation 'org.junit.jupiter:junit-jupiter-api:5.6.0'
    testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.6.0'
}

⚠️ Les variables ne peuvent pas figurer dès le début du fichier.

⚠️ Il faut faire attention à bien utiliser les double quotes. Certaines documentations comme celle de kotest donnent des instructions qui ne compilent pas.

Écrire des tests unitaires

Tests unitaires classiques

JUnit fonctionne très bien avec Kotlin sur JVM. Il suffit d’annoter la fonction avec @Test et d’utiliser un des assert. Voir documentation officielle pour ajouter la dépendance.

@Test
fun testSum() {
    val expected = 43
    assertEquals(expected, classForTesting.sum(40, 2))
}

Assertions et matchers

JUnit propose quelques assert classiques. On peut y ajouter les matchers de Kotest (une des sous-dépendances) pour avoir davantage de possibilités.

name shouldBe "sam"

"substring".shouldContain("str")
           .shouldBeLowerCase()

mylist.forExactly(3) {
    it.city shouldBe "Chicago"
}

Parameterized tests

JUnit propose un moyen de rendre une fonction de test paramétrable grâce aux annotations @ParameterizedTest et @MethodSource. La fonction de test acceptera alors des paramètres en entrée pour jouer le scénario avec différentes valeurs. Kotest le permet également à sa façon.

internal class MarsRoverTest {

    companion object {
        @JvmStatic
        fun leftCommands() = listOf(
            Arguments.of("N", "W"),
            Arguments.of("W", "S"),
            Arguments.of("E", "N"),
            Arguments.of("S", "E")
        )
    }

    @ParameterizedTest(name = "The new direction is {1} with initial direction {0}")
    @MethodSource(value = ["leftCommands"])
    internal fun `Should turn left`(initialDirection: String, newDirection: String) {
        val world = World(3,3)
        val marsRover = MarsRover(position, initialDirection, world)

        marsRover.execute(listOf("L"))

        marsRover.direction shouldBe newDirection
    }
}

Property-based Testing

Kotest permet de faire du Property-based Testing via de nombreux générateurs et apporte plusieurs fonctionnalités (shrinking, repeatable random seeds, …). Ajout de la dépendance et utilisations sur la documentation officielle.

class PropertyExample: StringSpec({
   "String size" {
      checkAll<String, String> { a, b ->
         (a + b).length shouldHaveLength a.length + b.length
      }
   }
})

Code Coverage

Plus tard :)

Mutation Testing

Plus tard :)

Apprendre le langage

Selon les goûts et les couleurs, voici plusieurs moyen d’apprendre Kotlin.

REPL

Plus tard :)