Aller au contenu


Photo

FireMock

Mock Stub Swift

  • Please log in to reply
10 réponses à ce sujet

#1 Magiic

Magiic

    Ecabosseur en fèves

  • Membre
  • PipPipPipPip
  • 359 messages
  • LocationParis

Posté 04 février 2017 - 20:26

Salut !

 

J'écris ce sujet pour présenter un framework que j'ai créée récemment encore mais que j'ai en tête depuis près d'un an maintenant. C'est aussi mon premier projet swift open source. Je vais un peu raconter ma vie pour expliquer le framework donc ça peut fortement vous ennuyer  :lol:

 

Ce projet c'est ce que j'ai appelé FireMock : https://github.com/Magiic/FireMock. Qu'est ce que c'est ? Pouvoir utiliser des fichiers JSON, XML et autres intégrés dans l'application (j'y reviendrais plus tard) afin de les utiliser eux et non pas ceux retournés par le serveur. Ce qu'on appelle chez nous les mocks. D'ailleurs, j'ai appris il y a quelques jours encore que Mock et Stub sont en fait 2 choses distinctes bien que proches et naïvement je ne le savais pas.

 

Bref c'était l'intro. Mes motivations derrière ce framework ? La principale c'est qu'au boulot je travaille avec pas mal de gros clients ayant des applications fonctionnant avec des gros WS, il est donc primordial de pouvoir utiliser l'application avec des fichiers "mockés" qui pourront imiter les retours du serveur. Dans mon cas, c'est souvent utiliser car les WS sont indisponibles (en panne, pas encore prêts etc.).

La plupart des applications où j'ai pu mettre mes mains dessus avaient un défaut qui m'embêtaient énormément c'était la conception et l'intégration des Mocks. C'était plutôt chiant, à terme très complexe avec des conditions ou des limitations extrêmes !! À partir de là j'ai donc commencé à réfléchir à une façon plus simple, plus élégante, plus flexible, plus intuitive, plus agréable, plus, plus, plus, plus .. Voilà le point de départ.

 

Bien sur il y a d'autres librairies qui font très très bien ça. Je vais juste citer celui de Aligatr qui est énorme et qui répond à pas mal de problèmes. Mon but n'était pas de réinventer la roue mais plutôt d'imaginer une solution alternative pour répondre à mes besoins et à celui de mes collègues.

 

Les problèmes que j'ai identifiés (dans beaucoup d'applications) sont les suivantes :

 

  • Intégration complexe : Avec des centaines de Web Services et des projets de plusieurs centaines de lignes, c'est quelques fois difficile de s'y retrouver. Alors les applications n'avaient pas toujours la même conception, la même archi mais c'est un point qu'ils partageaient. 
  • Mock ou pas Mock : Souvent c'est soit on est en mock pour toutes les requêtes, soit on utilise les WS du serveur. 
  • Flexibilité réduite : C'était un fichier pour un WS. Comment on fait quand le WS peut retourner plusieurs réponses différentes ? Changer le mock systématiquement...

Je vous passe les autres problèmes, dépendantes de l'architecture des projets. Ce que je cherchais c'était un moyen d'associer facilement un mock à un WS sans les lier fortement, avec une flexibilité importante et davantage de polyvalence.

 

FireMock vient avec un protocol qui demande plusieurs informations, toutes ont une valeur par défaut excepté la fonction mockFile qui attend le nom du fichier qui sera utilisé en remplacement du retour de serveur. Je vous prends l'exemple que j'ai illustré sur ma page GitHub.

enum NewsMock: FireMockProtocol {
    case success
    case failedParameters

    public var bundle: Bundle { return Bundle.main }

    public var afterTime: TimeInterval { return 0.0 }

    public var parameters: [String]? { return nil }

    public var headers: [String : String]? { return nil }

    public var statusCode: Int { return 200 }

    public var httpVersion: String? { return "1.1" }

    public func mockFile() -> String {
        switch self {
        case .success:
            return "success.json"
        case .failedParameters:
            return "failedParameters.json"
        }
    }
} 

Ici j'utilise une énumération, ce que je conseille fortement, dans laquelle j'énumère tous les cas de réponses possibles du serveur. On peut y définir le code de status retourné, ses headers ou encore les paramètres. Les paramètres sont ce que j'ai ajouté tout récemment (je devais le faire que plus tard mais le boulot...  ::) ) et permettent de lister le nom des paramètres qui peuvent être présents dans l'url. Pour utiliser le mock avec un cas de réponses, il suffit juste d'ajouter ces lignes :

let mock = NewsMock.success
FireMock.register(mock: mock, httpMethod: .get, forURL: url, enabled: true) 

En gros, ici on enregistre le mock avec le cas "success" pour une url donnée et la méthode GET.

Ensuite ? ensuite ça marche tout seul. Il suffit de lancer sa requête comme on le fait d'habitude sans absolument rien changer (ou presque, j'y viens).

Derrière comment ça marche ? J'utilise la classe URLProtocol (et non ! ce n'est pas un protocol) qui me permet d'intercepter les requêtes et de retourner le data que je veux. Dans mon cas je retourne le fichier associé à "success" si l'url match avec l'implémentation du protocol.

 

Si on utilise une librairie tierce de networking comme Alamofire par exemple, il faut ajouter mon protocol à sa liste (que je montre sur Github) afin que FireMock puisse être capable d'intercepter les requêtes.

 

Les lignes de codes que je viens de montrer sont faciles à écrire et permettent de les séparer du reste du code. On peut même les mettre dans un fichier totalement indépendant. Ça rend tout plus facilement configurable.

 

Je le disais plus haut, mais les fichiers XML et autres qu'on utilise sont souvent ajoutés au target de l'application et si on prend pas garde et qu'on les supprime pas en release, ils accompagnent l'application ! Xcode met à disposition une variable qui permet de lui lister le nom des fichiers qui doivent être exclus. Mais ce que je recommande c'est plutôt un script qui va supprimer les fichiers en release après avoir construit le .app.

 

Bon c'était une présentation de FireMock et plus globalement des mocks dans une application mobile. C'est un framework que je suis tout juste en train de tester sur des projets clients. L'architecture n'est pas définitive, elle peut légèrement évoluer mais le principe lui restera. J'ai ajouté des tests unitaires mais il y a encore beaucoup à faire pour garantir que tout marche mais voilà.

 

Finalement le post est pas si long ! 


  • iLandes, colas_ et Jérémy aiment ceci

#2 Céroce

Céroce

    Mouleur de chocolats

  • Contrôleur d'arômes
  • 5 320 messages
  • LocationSaint-Leu-d'Esserent / France

Posté 06 février 2017 - 09:05

Bien sur il y a d'autres librairies qui font très très bien ça. Je vais juste citer celui de Aligatr qui est énorme et qui répond à pas mal de problèmes. Mon but n'était pas de réinventer la roue mais plutôt d'imaginer une solution alternative pour répondre à mes besoins et à celui de mes collègues.

Justement, quels sont ces besoins, et en quoi OHHTTPStubs n'y répond pas ?
(Ce n'est pas un troll. Je pense qu'il faut mettre en avant les spécificités de ta lib).


RenaudPradenc.com Je suis développeur iOS & Mac indépendant.

#3 Magiic

Magiic

    Ecabosseur en fèves

  • Membre
  • PipPipPipPip
  • 359 messages
  • LocationParis

Posté 06 février 2017 - 20:43

Non tu as raison.

 

C'est simplement une autre façon de faire. Je trouve pour moi l'exemple suivant plus clair par exemple.

enum NewsMock: FireMockProtocol {
        case successFill
        case successEmpty
        case failed

        func mockFile() -> String {
            switch self {
            case . successFill:
                return "successFill.json"
            case . successEmpty:
                return "successEmpty.json"
            case . failed:
                return "failed.json"
            }
        }

        var afterTime: TimeInterval {
            switch self {
            case .successFill:
                return 10.0
            default:
                return 0.0
            }
        }
        
    }

Je définis un ensemble de cas possible. Et ensuite j'enregistre le cas que je souhaites utiliser. Avec OHHttpStubs c'est dans le block. C'est 2 façons de faire.

 

Et puis il y a quelque chose que je souhaites faire, c'est pouvoir au runtime décider si je passe en mock ou pas et ça pour chacun de ceux que j'ai enregistré. Donc en gros, afficher un écran ou je liste tous les mocks qui ont été enregistré, et à l'aide d'un switch par exemple, activé ou non le mock pour une requête donnée. Concrètement je peux le faire et je trouve ça très intéressant.

 

Donc c'est pas qu'elle est mieux faite par rapport aux autres mais c'est qu'elle me convient davantage quand je développe. Je pense qu'il faut essayer sur des projets pour se faire un avis.



#4 Jérémy

Jérémy

    Ecabosseur en fèves

  • Membre
  • PipPipPipPip
  • 407 messages
  • LocationBordeaux

Posté 20 février 2017 - 11:34

J'aime beaucoup ton approche qui me semble bien plus intuitives et surtout lisible (qui est un gros avantage pour la maintenance d'une application). :bravo!:

 

On peut le tester quand ?

 

Il est vrai que pour trop de développeurs, les applis mobiles sont des devs de seconde zone qui ne nécessitent pas de tests unitaire et poutant........ ::)


Twitter : @jrmguimberteau


#5 Céroce

Céroce

    Mouleur de chocolats

  • Contrôleur d'arômes
  • 5 320 messages
  • LocationSaint-Leu-d'Esserent / France

Posté 20 février 2017 - 16:47

Il est vrai que pour trop de développeurs, les applis mobiles sont des devs de seconde zone qui ne nécessitent pas de tests unitaire et poutant........ ::)

Si tu crois que c'est seulement pour les applis mobiles… c'est pareil partout, je l'ai vu dernièrement pour du node.js.
RenaudPradenc.com Je suis développeur iOS & Mac indépendant.

#6 Jérémy

Jérémy

    Ecabosseur en fèves

  • Membre
  • PipPipPipPip
  • 407 messages
  • LocationBordeaux

Posté 20 février 2017 - 16:55

Si tu crois que c'est seulement pour les applis mobiles… c'est pareil partout, je l'ai vu dernièrement pour du node.js.

 

En même temps, tester c'est douter ! xd


Twitter : @jrmguimberteau


#7 Magiic

Magiic

    Ecabosseur en fèves

  • Membre
  • PipPipPipPip
  • 359 messages
  • LocationParis

Posté 20 février 2017 - 19:46

Tu peux le tester quand tu veux. Il est dispo sur Github. Mais c'est vrai que je n'ai pas mis le lien.

 

https://github.com/Magiic/FireMock

 

Du coup j'ai modifié et ajouter de nouvelles choses.

 

  • Possibilité de passer un regex au lieu d'une url lors de l'enregistrement du mock pour davantage de flexibilité. Dans les cas que je rencontre par exemple, j'ai souvent des path qui sont dynamiques et qui dépendent du contexte de l'application (après authentification par exemple). Donc ça permet de ne pas forcément enregistrer le mock lorsqu'on a les données.
  • Un service qui permet de voir tous les mocks enregistrés dans l'application, pour quel url associé et avec quels paramètres s'ils existent. Il permet d'activer ou désactiver un mock en particulier ou l'ensemble des mocks. Donc quand on veut appeler les WS ou les mock plus besoin de modifier le code et recompiler.
  • Prise en charge automatiquement dans les frameworks de gestion de requêtes comme Alamofire en utilisant l'objc-runtime avec la méthode dite "Swizzling". Donc plus besoin d'ajouter le code manuellement sauf si on utilise toujours NSURLConnection.
  • Un debug pour voir quel mock sont ajoutés et quelles requêtes sont interceptées.

J'ai densifié les test-unitaires pour avoir une couverture de code d'environ 60%. J'aimerais bien passer à 80% si possible.



#8 Jérémy

Jérémy

    Ecabosseur en fèves

  • Membre
  • PipPipPipPip
  • 407 messages
  • LocationBordeaux

Posté 20 février 2017 - 20:41

Merci ! Joli travail !  :bravo!:

 

Sur la page GitHub du projet tu aurais pu trouver un autre exemple que ça : 

 

 

You can use your mock files with specific hosts only. If empty, mock works for all hosts.


FireMock.onlyHosts = ["xxx.com"]

 

 

:D


Twitter : @jrmguimberteau


#9 Magiic

Magiic

    Ecabosseur en fèves

  • Membre
  • PipPipPipPip
  • 359 messages
  • LocationParis

Posté 20 février 2017 - 21:49

Oui y'a toujours mieux à faire :)



#10 Jérémy

Jérémy

    Ecabosseur en fèves

  • Membre
  • PipPipPipPip
  • 407 messages
  • LocationBordeaux

Posté 20 février 2017 - 22:53

Non mais l'idée est intéressante. Alors même si par certains aspects il possible de ce dire "voici un nouveau framework qui fait la même chose que les autres", il y a quand même l'idée d'une approche différente qui me semble bien foutu.  ;)

 

Après il faut voir si tu as le temps mais un p'tit tuto qu illustre le projet par un exemple d'utilisation tout en mettant en avant ce qui le différencie des autres pourrait inciter les devs à se dire "je vais essayer". En tout cas je ne peux que saluer ton initiative de "simplifier" la mise en place de tests.  :)


Twitter : @jrmguimberteau


#11 Magiic

Magiic

    Ecabosseur en fèves

  • Membre
  • PipPipPipPip
  • 359 messages
  • LocationParis

Posté 21 février 2017 - 23:04

Merci !

 

Un projet d'exemple j'en ai déjà 1 en fait. Je pense à le faire mais c'est pas le truc le plus important dans ma short list  xd

 

Je trouve déjà que c'est plutôt très simple à comprendre et à intégrer même si un projet d'exemple serait un plus. Pour les différences avec les autres librairies elles sont peu nombreuses mais je les expliquent via les fonctionnalités présentes dans le readme.

Je pense qu'après c'est en fonction de ses usages et son feeling.


  • Jérémy aime ceci





Also tagged with one or more of these keywords: Mock, Stub, Swift

0 utilisateur(s) li(sen)t ce sujet

0 membre(s), 0 invité(s), 0 utilisateur(s) anonyme(s)