Tester à Kotlin avec Spock

Le but de cet article est de montrer quelles difficultés surviennent lors de l'utilisation de Spock avec Kotlin, quelles sont les façons de les résoudre et de répondre à la question de savoir s'il vaut la peine d'utiliser Spock si vous développez sur Kotlin. Détails sous la coupe.

Je travaille pour une entreprise qui pratique une programmation extrême . L'une des principales méthodes de programmation extrême que nous utilisons dans notre travail quotidien est le TDD (développement piloté par les tests). Cela signifie qu'avant de changer le code, nous écrivons un test qui couvre le changement souhaité. Ainsi, nous écrivons régulièrement des tests et avons une couverture de code avec des tests proches de 100%. Cela impose certaines conditions pour choisir un framework de test: c'est une chose d'écrire des tests une fois par semaine, c'est une autre chose de le faire tous les jours.

Nous développons sur Kotlin et à un moment donné, nous avons choisi Spock comme framework principal. Environ 6 mois se sont écoulés depuis ce moment, un sentiment d'euphorie et de nouveauté s'est écoulé, donc l'article est une sorte de rétrospective dans laquelle je vais essayer de vous dire quelles difficultés nous avons rencontrées pendant cette période et comment nous les avons résolues.

Pourquoi exactement Spock?




Tout d'abord, vous devez déterminer quels frameworks permettent de tester Kotlin et quels avantages Spock offre par rapport à eux.

L'un des avantages de Kotlin est sa compatibilité avec Java, qui vous permet d'utiliser tous les frameworks Java pour les tests, tels que Junit , TestNG , Spock , etc. Dans le même temps, il existe des cadres conçus spécifiquement pour Kotlin tels que Spek et Kotest . Pourquoi avons-nous choisi Spock?

Je voudrais souligner les avantages suivants:

  • Tout d'abord, Spock est écrit en Groovy. Bon ou mauvais - jugez par vous-même. À propos de Groovy, vous pouvez lire l'article ici . Groovy m'attire personnellement pour la syntaxe laconique, la présence de typage dynamique, la syntaxe intégrée pour les listes, les tableaux réguliers et associatifs et les conversions de types complètement folles.
  • Deuxièmement, Spock contient déjà un cadre factice (MockingApi) et une bibliothèque d'assertions;
  • Spock est également idéal pour écrire des tests paramétrés:

def "maximum of two numbers"() {
    expect:
    Math.max(a, b) == c

    where:
    a | b | c
    1 | 3 | 3
    7 | 4 | 7
    0 | 0 | 0
  }

C'est une comparaison très superficielle (si cela peut être appelé une comparaison), mais ce sont à peu près les raisons pour lesquelles nous avons choisi Spock. Naturellement, au cours des semaines et des mois suivants, nous avons rencontré des difficultés.

Problèmes rencontrés lors du test de Kotlin avec Spock et de leur résolution


Ici, je fais immédiatement une réserve que les problèmes décrits ci-dessous ne sont pas propres à Spock, ils seront pertinents pour n'importe quel framework Java et ils sont principalement associés à la compatibilité avec Kotlin .

1. final par défaut


Problème

Contrairement à Java, toutes les classes Kotlin ont un modificateur par défaut final, ce qui empêche d'autres descendants et substitutions de méthode. Le problème ici est que lors de la création d'objets fictifs de n'importe quelle classe, la plupart des frameworks fictifs essaient de créer un objet proxy, qui n'est qu'un descendant de la classe d'origine et remplace ses méthodes.

Par conséquent, si vous avez un service:

class CustomerService {
    fun getCustomer(id: String): Customer {
        // Some logic
    }
}

et vous essayez de créer une maquette de ce service dans Groovy:

def customerServiceMock = Mock(CustomerService)

alors vous obtenez une erreur:
org.spockframework.mock.CannotCreateMockException: Cannot create mock for class CustomerService because Java mocks cannot mock final classes

Décision:

  • , , open, . «» , , , , ;
  • all-open . . Spring Framework Hibernate, cglib(Code Generation Library);
  • , mock-, Kotlin (, mockk). , , Spock Spock MockingApi.

2.


Problème

Dans Kotlin, les arguments de fonction ou de constructeur peuvent avoir des valeurs par défaut qui sont utilisées si l'argument de fonction n'est pas spécifié lors de son appel. La difficulté est que le bytecode Java perd les valeurs d'argument par défaut et les noms de paramètres de fonction et, par conséquent, lors de l'appel d'une fonction ou d'un constructeur Kotlin à partir de Spock, toutes les valeurs doivent être explicitement spécifiées.

Considérez une classe Customerqui a un constructeur avec 2 champs obligatoires emailet nameet des champs facultatifs qui ont des valeurs par défaut:

data class Customer(
    val email: String,
    val name: String,
    val surname: String = "",
    val age: Int = 18,
    val identifiers: List<NationalIdentifier> = emptyList(),
    val addresses: List<Address> = emptyList(),
    val paymentInfo: PaymentInfo? = null
)

Afin de créer une instance de cette classe dans le test Groovy Spock, vous devrez passer des valeurs pour tous les arguments au constructeur:

new Customer("john.doe@gmail.com", "John", "", 18, [], [], null)

Décision:


class ModelFactory {
    static def getCustomer() { 
        new Customer(
            "john.doe@gmail.com", 
            "John", 
            "Doe", 
            18, 
            [nationalIdentifier], 
            [address], 
            paymentInfo
        ) 
    }

    static def getAddress() { new Address(/* arguments */) }

    static def getNationalIdentifier() { new NationalIdentifier(/* arguments */) }

    static def getPaymentInfo() { new PaymentInfo(/* arguments */) }
}

3. Java Reflection vs Kotlin Reflection


Cela peut être une zone assez étroite, mais si vous prévoyez d'utiliser la réflexion dans vos tests, vous devriez surtout penser à utiliser Groovy. Dans notre projet, nous utilisons beaucoup d'annotations et, pour ne pas oublier d'annoter certaines classes ou certains champs avec des annotations, nous écrivons des tests par réflexion. C'est là que surgissent les difficultés.

Problème Les

classes et les annotations sont écrites dans Kotlin, la logique liée aux tests et à la réflexion dans Groovy. Pour cette raison, les tests produisent un hachage à partir de l'API Java Reflection et de Kotlin Reflection:

Conversion des classes Java en Kotlin:

def kotlinClass = new KClassImpl(clazz)

Appel des fonctions d'extension Kotlin en tant que méthodes statiques:
ReflectJvmMapping.getJavaType(property.returnType)

Code difficile à lire où les types Kotlin seront utilisés:
private static boolean isOfCollectionType(KProperty1<Object, ?> property) {
    // Some logic
}

Bien sûr, ce n'est pas quelque chose d'excessif ou d'impossible. La seule question est, n'est-il pas plus facile de le faire avec le framework de test Kotlin, où il sera possible d'utiliser des fonctions d'extension et de la pure réflexion Kotlin ??

4. Coroutines


Si vous prévoyez d'utiliser des coroutines, c'est une autre raison de penser si vous souhaitez choisir le framework Kotlin pour les tests.

Problème

Si vous créez une suspendfonction:

suspend fun someSuspendFun() {
    // Some logic
}

, puis il compile comme suit:

public void someSuspendFun(Continuation<? super Unit> $completion) {
    // Some logic
}

où Continuation est une classe Java qui encapsule la logique d'exécution de la coroutine. C'est là que se pose le problème, comment créer un objet de cette classe? En outre, runBlocking n'est pas disponible dans Groovy . Et donc, il n'est généralement pas très clair comment tester le code avec des coroutines dans Spock.

Sommaire


Plus de six mois d'utilisation de Spock, cela nous a apporté plus de bien que de mal et nous ne regrettons pas le choix: les tests sont faciles à lire et à maintenir, écrire des tests paramétrés est un plaisir. Une mouche dans la pommade dans notre cas s'est avérée être un reflet, mais elle n'est utilisée que dans quelques endroits, la même chose avec les coroutines.

Lorsque vous choisissez un cadre pour tester un projet en cours de développement sur Kotlin, vous devez réfléchir attentivement aux fonctionnalités du langage que vous prévoyez d'utiliser. Si vous écrivez une simple application CRUD, Spock est la solution parfaite. Utilisez-vous des coroutines et de la réflexion? Alors, regardez mieux Spek ou Kotest .

Le matériel ne prétend pas être complet, donc si vous avez rencontré d'autres difficultés lors de l'utilisation de Spock et Kotlin - écrivez dans les commentaires.

Liens utiles



All Articles