Tutoriel sur les tests unitaires et les tests d’interface utilisateur pour iOS

Note de mise à jour : Michael Katz a mis à jour ce tutoriel pour Xcode 10.1, Swift 4.2 et iOS 12. Audrey Tam a écrit l’original.

Écrire des tests n’est pas glamour, mais comme les tests empêchent votre application pétillante de se transformer en un tas de ferraille truffé de bogues, c’est nécessaire. Si vous lisez ce tutoriel, vous savez déjà que vous devez écrire des tests pour votre code et votre interface utilisateur, mais vous ne savez peut-être pas comment.

Vous avez peut-être une application qui fonctionne, mais vous voulez tester les changements que vous faites pour étendre l’application. Peut-être avez-vous déjà écrit des tests, mais vous n’êtes pas sûr qu’il s’agisse des bons tests. Ou encore, vous avez commencé à travailler sur une nouvelle app et vous voulez tester au fur et à mesure.

Ce tutoriel va vous montrer :

  • Comment utiliser le navigateur de test de Xcode pour tester le modèle et les méthodes asynchrones d’une app
  • Comment simuler des interactions avec des objets de bibliothèque ou de système en utilisant des stubs et des mocks
  • Comment tester l’interface utilisateur et les performances
  • Comment utiliser l’outil de couverture de code

En cours de route, vous apprendrez une partie du vocabulaire utilisé par les ninjas du test.

Découvrir ce qu’il faut tester

Avant d’écrire des tests, il est important de connaître les bases. Que devez-vous tester ?

Si votre objectif est d’étendre une application existante, vous devez d’abord écrire des tests pour tout composant que vous prévoyez de modifier.

Généralement, les tests doivent couvrir :

  • Les fonctionnalités de base : Les classes et les méthodes du modèle et leurs interactions avec le contrôleur
  • Les flux de travail les plus courants de l’interface utilisateur
  • Les conditions limites
  • Les corrections de bugs

Bonnes pratiques de test

L’acronyme FIRST décrit un ensemble concis de critères pour des tests unitaires efficaces. Ces critères sont :

  • Rapide : Les tests doivent s’exécuter rapidement.
  • Indépendants/isolés : Les tests ne doivent pas partager d’état entre eux.
  • Répétable : Vous devez obtenir les mêmes résultats à chaque fois que vous exécutez un test. Les fournisseurs de données externes ou les problèmes de concurrence pourraient provoquer des échecs intermittents.
  • Auto-validation : Les tests devraient être entièrement automatisés. La sortie devrait être soit « pass » ou « fail », plutôt que de s’appuyer sur l’interprétation d’un fichier journal par un programmeur.
  • En temps opportun : Idéalement, les tests devraient être écrits avant que vous n’écriviez le code de production qu’ils testent (Test-Driven Development).

Suivre les principes du FIRST permettra de garder vos tests clairs et utiles, au lieu de se transformer en barrages routiers pour votre application.

Démarrer

Démarrez en téléchargeant les matériaux du projet en utilisant le bouton Télécharger les matériaux en haut ou en bas de ce tutoriel. Il existe deux projets de démarrage distincts : BullsEye et HalfTunes.

  • BullsEye est basé sur un exemple d’application dans iOS Apprentice. La logique du jeu se trouve dans la classe BullsEyeGame, que vous testerez au cours de ce tutoriel.
  • HalfTunes est une version mise à jour de l’application exemple du tutoriel URLSession. Les utilisateurs peuvent interroger l’API iTunes pour trouver des chansons, puis télécharger et lire des extraits de chansons.

Tests unitaires dans Xcode

Le navigateur Test fournit le moyen le plus simple de travailler avec des tests ; vous l’utiliserez pour créer des cibles de test et exécuter des tests contre votre application.

Création d’une cible de test unitaire

Ouvrez le projet BullsEye et appuyez sur Command-6 pour ouvrir le navigateur de test.

Cliquez sur le bouton + dans le coin inférieur gauche, puis sélectionnez Nouvelle cible de test unitaire… dans le menu :

Acceptez le nom par défaut, BullsEyeTests. Lorsque le bundle de test apparaît dans le navigateur de test, cliquez sur pour ouvrir le bundle dans l’éditeur. Si le faisceau n’apparaît pas automatiquement, dépannez en cliquant sur l’un des autres navigateurs, puis revenez au navigateur Test.

Le modèle par défaut importe le cadre de test, XCTest, et définit une BullsEyeTests sous-classe de XCTestCase, avec setUp(), tearDown(), et des méthodes de test d’exemple.

Il existe trois façons d’exécuter les tests :

  1. Produit ▸ Test ou Commande-U. Ces deux méthodes exécutent toutes les classes de test.
  2. Cliquez sur le bouton en forme de flèche dans le navigateur de test.
  3. Cliquez sur le bouton en forme de diamant dans la gouttière.

Vous pouvez également exécuter une méthode de test individuelle en cliquant sur son losange, soit dans le navigateur de test, soit dans la gouttière.

Essayez les différentes façons d’exécuter les tests pour avoir une idée du temps que cela prend et de ce à quoi cela ressemble. Les tests d’exemple ne font encore rien, donc ils s’exécutent très rapidement !

Lorsque tous les tests réussissent, les diamants deviennent verts et affichent des coches. Vous pouvez cliquer sur le diamant gris à la fin de testPerformanceExample() pour ouvrir le résultat de performance:

Vous n’avez pas besoin de testPerformanceExample() ou testExample() pour ce tutoriel, alors supprimez-les.

Utiliser XCTAssert pour tester les modèles

D’abord, vous utiliserez les fonctions XCTAssert pour tester une fonction centrale du modèle de BullsEye : Est-ce qu’un BullsEyeGameobjet calcule correctement le score d’un tour ?

Dans BullsEyeTests.swift, ajoutez cette ligne juste en dessous de l’instruction import:

@testable import BullsEye

Cela donne aux tests unitaires l’accès aux types et fonctions internes de BullsEye.

Au sommet de la classe BullsEyeTests, ajoutez cette propriété:

var sut: BullsEyeGame!

Cela crée un placeholder pour un BullsEyeGame, qui est le système sous test (SUT), ou l’objet que cette classe de cas de test est concernée par le test.

Puis, remplacez le contenu de setup() par ceci :

super.setUp()sut = BullsEyeGame()sut.startNewGame()

Cela crée un objet BullsEyeGame au niveau de la classe, de sorte que tous les tests de cette classe de test peuvent accéder aux propriétés et aux méthodes de l’objet SUT.

Ici, vous appelez également le startNewGame() du jeu, qui initialise le targetValue. De nombreux tests utiliseront targetValue pour vérifier que le jeu calcule correctement le score.

Avant d’oublier, libérez votre objet SUT dans tearDown(). Remplacez son contenu par :

sut = nilsuper.tearDown()
Remarque : C’est une bonne pratique de créer le SUT dans setUp() et de le libérer dans tearDown() pour s’assurer que chaque test commence avec une ardoise propre. Pour plus de discussion, consultez le post de Jon Reid sur le sujet.

Écrire votre premier test

Maintenant, vous êtes prêt à écrire votre premier test !

Ajoutez le code suivant à la fin de BullsEyeTests:

func testScoreIsComputed() { // 1. given let guess = sut.targetValue + 5 // 2. when sut.check(guess: guess) // 3. then XCTAssertEqual(sut.scoreRound, 95, "Score computed from guess is wrong")}

Le nom d’une méthode de test commence toujours par test, suivi d’une description de ce qu’elle teste.

C’est une bonne pratique de formater le test en sections given, when et then:

  1. Given : Ici, vous configurez toutes les valeurs nécessaires. Dans cet exemple, vous créez une valeur guess afin de pouvoir préciser de combien elle diffère de targetValue.
  2. When : Dans cette section, vous allez exécuter le code testé : Call check(guess:).
  3. Then : C’est la section où vous allez affirmer le résultat attendu avec un message qui s’imprime si le test échoue. Dans ce cas, sut.scoreRound devrait être égal à 95 (100 – 5).

Lancez le test en cliquant sur l’icône en forme de diamant dans la gouttière ou dans le navigateur de test. Cela construira et exécutera l’application, et l’icône en forme de diamant se transformera en une coche verte !

Note : Pour voir une liste complète des XCTestAssertions, allez sur Apple’s Assertions Listed by Category.

Débogage d’un test

Il y a un bogue intégré à dessein dans BullsEyeGame, et vous allez vous entraîner à le trouver maintenant. Pour voir le bogue en action, vous allez créer un test qui soustrait 5 de targetValue dans la section donnée, et laisse tout le reste inchangé.

Ajouter le test suivant :

func testScoreIsComputedWhenGuessLTTarget() { // 1. given let guess = sut.targetValue - 5 // 2. when sut.check(guess: guess) // 3. then XCTAssertEqual(sut.scoreRound, 95, "Score computed from guess is wrong")}

La différence entre guess et targetValue est toujours de 5, donc le score devrait toujours être de 95.

Dans le navigateur de points d’arrêt, ajoutez un point d’arrêt d’échec de test. Cela arrêtera l’exécution du test lorsqu’une méthode de test affiche une assertion d’échec.

Exécutez votre test, et il devrait s’arrêter à la ligne XCTAssertEqual avec un échec de test.

Inspectez sut et guess dans la console de débogage :

guess est targetValue - 5 mais scoreRound est 105, pas 95 !

Pour investiguer davantage, utilisez le processus de débogage normal : Définissez un point d’arrêt à l’instruction when et également un dans BullsEyeGame.swift, à l’intérieur de check(guess:), où il crée difference. Ensuite, exécutez à nouveau le test, et passez sur l’instruction let difference pour inspecter la valeur de difference dans l’application :

Le problème est que difference est négatif, donc le score est 100 – (-5). Pour résoudre ce problème, vous devez utiliser la valeur absolue de difference. Dans check(guess:), décommentez la ligne correcte et supprimez la ligne incorrecte.

Supprimez les deux points d’arrêt, et exécutez à nouveau le test pour confirmer qu’il réussit maintenant.

Utilisation de XCTestExpectation pour tester les opérations asynchrones

Maintenant que vous avez appris à tester les modèles et à déboguer les échecs de test, il est temps de passer au test du code asynchrone.

Ouvrez le projet HalfTunes. Il utilise URLSession pour interroger l’API iTunes et télécharger des échantillons de chansons. Supposons que vous vouliez le modifier pour utiliser AlamoFire pour les opérations réseau. Pour voir si quelque chose se casse, vous devez écrire des tests pour les opérations réseau et les exécuter avant et après avoir modifié le code.

URLSession Les méthodes sont asynchrones : elles retournent tout de suite, mais ne finissent de s’exécuter que plus tard. Pour tester les méthodes asynchrones, vous utilisez XCTestExpectation pour que votre test attende que l’opération asynchrone se termine.

Les tests asynchrones sont généralement lents, vous devez donc les séparer de vos tests unitaires plus rapides.

Créer une nouvelle cible de test unitaire nommée HalfTunesSlowTests. Ouvrez la classe HalfTunesSlowTests, et importez le module HalfTunes app juste en dessous de l’instruction import existante :

@testable import HalfTunes

Tous les tests de cette classe utilisent le URLSession par défaut pour envoyer des requêtes aux serveurs d’Apple, donc déclarez un objet sut, créez-le dans setUp() et libérez-le dans tearDown().

Remplacez le contenu de la classe HalfTunesSlowTests par :

var sut: URLSession!override func setUp() { super.setUp() sut = URLSession(configuration: .default)}override func tearDown() { sut = nil super.tearDown()}

Puis, ajoutez ce test asynchrone :

// Asynchronous test: success fast, failure slowfunc testValidCallToiTunesGetsHTTPStatusCode200() { // given let url = URL(string: "https://itunes.apple.com/search?media=music&entity=song&term=abba") // 1 let promise = expectation(description: "Status code: 200") // when let dataTask = sut.dataTask(with: url!) { data, response, error in // then if let error = error { XCTFail("Error: \(error.localizedDescription)") return } else if let statusCode = (response as? HTTPURLResponse)?.statusCode { if statusCode == 200 { // 2 promise.fulfill() } else { XCTFail("Status code: \(statusCode)") } } } dataTask.resume() // 3 wait(for: , timeout: 5)}

Ce test vérifie que l’envoi d’une requête valide à iTunes renvoie un code d’état 200. La plupart du code est le même que celui que vous écririez dans l’application, avec ces lignes supplémentaires:

  1. expectation(description :): Renvoie un objet XCTestExpectation, stocké dans promise. Le paramètre description décrit ce que vous attendez qu’il se passe.
  2. promise.fulfill() : Appelez ceci dans la fermeture de la condition de succès du gestionnaire d’achèvement de la méthode asynchrone pour signaler que l’attente a été satisfaite.
  3. wait(for:timeout :): Maintient l’exécution du test jusqu’à ce que toutes les attentes soient satisfaites ou que l’intervalle timeout se termine, selon ce qui se produit en premier.

Exécutez le test. Si vous êtes connecté à Internet, le test devrait prendre environ une seconde pour réussir après le chargement de l’application dans le simulateur.

Echec rapide

L’échec fait mal, mais il n’a pas à prendre une éternité.

Pour expérimenter l’échec, il suffit de supprimer le ‘s’ de « itunes » dans l’URL:

let url = URL(string: "https://itune.apple.com/search?media=music&entity=song&term=abba")

Lancer le test. Il échoue, mais il prend l’intervalle de temps complet ! C’est parce que vous avez supposé que la requête réussirait toujours, et c’est là que vous avez appelé promise.fulfill(). Comme la requête a échoué, elle ne s’est terminée que lorsque le délai d’attente a expiré.

Vous pouvez améliorer cela et faire échouer le test plus rapidement en changeant l’hypothèse : Au lieu d’attendre que la requête réussisse, attendez seulement que le gestionnaire d’achèvement de la méthode asynchrone soit invoqué. Cela se produit dès que l’application reçoit une réponse – OK ou erreur – du serveur, qui répond à l’attente. Votre test peut alors vérifier si la requête a réussi.

Pour voir comment cela fonctionne, créez un nouveau test.

Mais d’abord, corrigez le test précédent en annulant la modification que vous avez apportée au url.
Puis, ajoutez le test suivant à votre classe :

func testCallToiTunesCompletes() { // given let url = URL(string: "https://itune.apple.com/search?media=music&entity=song&term=abba") let promise = expectation(description: "Completion handler invoked") var statusCode: Int? var responseError: Error? // when let dataTask = sut.dataTask(with: url!) { data, response, error in statusCode = (response as? HTTPURLResponse)?.statusCode responseError = error promise.fulfill() } dataTask.resume() wait(for: , timeout: 5) // then XCTAssertNil(responseError) XCTAssertEqual(statusCode, 200)}

La différence essentielle est que le simple fait d’entrer dans le gestionnaire d’achèvement répond à l’attente, et cela ne prend qu’une seconde environ. Si la requête échoue, les assertions then échouent.

Exécutez le test. Il devrait maintenant prendre environ une seconde pour échouer. Il échoue parce que la requête a échoué, et non parce que l’exécution du test a dépassé timeout.

Corrigez le url, puis exécutez à nouveau le test pour confirmer qu’il réussit maintenant.

Faking Objects and Interactions

Les tests asynchrones vous donnent la confiance que votre code génère une entrée correcte vers une API asynchrone. Vous pourriez également vouloir tester que votre code fonctionne correctement lorsqu’il reçoit une entrée d’un URLSession, ou qu’il met correctement à jour la base de données par défaut de l’utilisateur ou un conteneur iCloud.

La plupart des apps interagissent avec des objets système ou de bibliothèque – des objets que vous ne contrôlez pas – et les tests qui interagissent avec ces objets peuvent être lents et non répétables, violant deux des principes FIRST. Au lieu de cela, vous pouvez simuler les interactions en obtenant des entrées à partir de stubs ou en mettant à jour des objets fantaisie.

Employez la simulation lorsque votre code a une dépendance sur un objet système ou bibliothèque. Vous pouvez le faire en créant un faux objet pour jouer ce rôle et en injectant ce faux dans votre code. Dependency Injection de Jon Reid décrit plusieurs façons de le faire.

Fake Input From Stub

Dans ce test, vous allez vérifier que le updateSearchResults(_:) de l’app analyse correctement les données téléchargées par la session, en vérifiant que searchResults.count est correct. Le SUT est le contrôleur de vue, et vous allez simuler la session avec des stubs et quelques données téléchargées au préalable.

Allez dans le navigateur de test et ajoutez une nouvelle cible de test unitaire. Nommez-la HalfTunesFakeTests. Ouvrez HalfTunesFakeTests.swift et importez le module HalfTunes app juste en dessous de la déclaration import:

@testable import HalfTunes

Maintenant, remplacez le contenu de la classe HalfTunesFakeTests par ceci:

var sut: SearchViewController!override func setUp() { super.setUp() sut = UIStoryboard(name: "Main", bundle: nil) .instantiateInitialViewController() as? SearchViewController}override func tearDown() { sut = nil super.tearDown()}

Cela déclare le SUT, qui est un SearchViewController, le crée dans setUp() et le libère dans tearDown() :

Note : Le SUT est le contrôleur de vue, car HalfTunes a un problème massif de contrôleur de vue – tout le travail est fait dans SearchViewController.rapide. Déplacer le code de mise en réseau dans un module séparé réduirait ce problème et, aussi, rendrait les tests plus faciles.

Puis, vous aurez besoin d’un échantillon de données JSON que votre fausse session fournira à votre test. Quelques éléments suffiront, donc pour limiter vos résultats de téléchargement dans iTunes, ajoutez &limit=3 à la chaîne d’URL:

https://itunes.apple.com/search?media=music&entity=song&term=abba&limit=3

Copiez cette URL et collez-la dans un navigateur. Cela télécharge un fichier nommé 1.txt, 1.txt.js ou similaire. Prévisualisez-le pour confirmer qu’il s’agit d’un fichier JSON, puis renommez-le abbaData.json.

Maintenant, retournez dans Xcode et allez dans le navigateur de projet. Ajoutez le fichier au groupe HalfTunesFakeTests.

Le projet HalfTunes contient le fichier de support DHURLSessionMock.swift. Celui-ci définit un protocole simple nommé DHURLSession, avec des méthodes (stubs) pour créer une tâche de données avec soit un URL soit un URLRequest. Il définit également URLSessionMock, qui se conforme à ce protocole avec des initialisateurs qui vous permettent de créer un objet fantaisie URLSession avec votre choix de données, de réponse et d’erreur.

Pour mettre en place le faux, allez dans HalfTunesFakeTests.swift et ajoutez ce qui suit dans setUp(), après l’instruction qui crée le SUT :

let testBundle = Bundle(for: type(of: self))let path = testBundle.path(forResource: "abbaData", ofType: "json")let data = try? Data(contentsOf: URL(fileURLWithPath: path!), options: .alwaysMapped)let url = URL(string: "https://itunes.apple.com/search?media=music&entity=song&term=abba")let urlResponse = HTTPURLResponse( url: url!, statusCode: 200, httpVersion: nil, headerFields: nil)let sessionMock = URLSessionMock(data: data, response: urlResponse, error: nil)sut.defaultSession = sessionMock

Ceci met en place les fausses données et réponses et crée le faux objet de session. Enfin, à la fin, il injecte la fausse session dans l’application comme une propriété de sut.

Maintenant, vous êtes prêt à écrire le test qui vérifie si l’appel à updateSearchResults(_:) analyse les fausses données. Ajoutez le test suivant:

func test_UpdateSearchResults_ParsesData() { // given let promise = expectation(description: "Status code: 200") // when XCTAssertEqual( sut.searchResults.count, 0, "searchResults should be empty before the data task runs") let url = URL(string: "https://itunes.apple.com/search?media=music&entity=song&term=abba") let dataTask = sut.defaultSession.dataTask(with: url!) { data, response, error in // if HTTP request is successful, call updateSearchResults(_:) // which parses the response data into Tracks if let error = error { print(error.localizedDescription) } else if let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 { self.sut.updateSearchResults(data) } promise.fulfill() } dataTask.resume() wait(for: , timeout: 5) // then XCTAssertEqual(sut.searchResults.count, 3, "Didn't parse 3 items from fake response")}

Vous devez toujours écrire ceci comme un test asynchrone parce que le stub prétend être une méthode asynchrone.

L’assertion when est que searchResults est vide avant l’exécution de la tâche de données. Cela devrait être vrai, car vous avez créé un tout nouveau SUT dans setUp().

Les fausses données contiennent le JSON de trois objets Track, donc l’assertion then est que le tableau searchResults du contrôleur de vue contient trois éléments.

Exécutez le test. Il devrait réussir assez rapidement car il n’y a pas de réelle connexion réseau !

Fausse mise à jour de l’objet fantaisie

Le test précédent a utilisé un stub pour fournir une entrée à partir d’un faux objet. Ensuite, vous allez utiliser un objet fictif pour tester que votre code met correctement à jour UserDefaults.

Rouvrez le projet BullsEye. L’application possède deux styles de jeu : L’utilisateur déplace le curseur pour correspondre à la valeur cible ou devine la valeur cible à partir de la position du curseur. Un contrôle segmenté dans le coin inférieur droit change le style de jeu et l’enregistre dans les valeurs par défaut de l’utilisateur.

Votre prochain test vérifiera que l’appli enregistre correctement la propriété gameStyle.

Dans le navigateur de test, cliquez sur Nouvelle classe de test unitaire et nommez-la BullsEyeMockTests. Ajoutez ce qui suit sous l’instruction import:

@testable import BullsEyeclass MockUserDefaults: UserDefaults { var gameStyleChanged = 0 override func set(_ value: Int, forKey defaultName: String) { if defaultName == "gameStyle" { gameStyleChanged += 1 } }}

MockUserDefaults Remplace set(_:forKey:) pour incrémenter le drapeau gameStyleChanged. Souvent, vous verrez des tests similaires qui définissent une variable Bool, mais l’incrémentation d’un Int vous donne plus de flexibilité – par exemple, votre test pourrait vérifier que la méthode est appelée une seule fois.

Déclarez le SUT et l’objet fantaisie dans BullsEyeMockTests:

var sut: ViewController!var mockUserDefaults: MockUserDefaults!

Puis, remplacez les setUp() et tearDown() par défaut par ceci:

override func setUp() { super.setUp() sut = UIStoryboard(name: "Main", bundle: nil) .instantiateInitialViewController() as? ViewController mockUserDefaults = MockUserDefaults(suiteName: "testing") sut.defaults = mockUserDefaults}override func tearDown() { sut = nil mockUserDefaults = nil super.tearDown()}

Cela crée le SUT et l’objet fantaisie et injecte l’objet fantaisie comme une propriété du SUT.

Maintenant, remplacez les deux méthodes de test par défaut dans le modèle par ceci:

func testGameStyleCanBeChanged() { // given let segmentedControl = UISegmentedControl() // when XCTAssertEqual( mockUserDefaults.gameStyleChanged, 0, "gameStyleChanged should be 0 before sendActions") segmentedControl.addTarget(sut, action: #selector(ViewController.chooseGameStyle(_:)), for: .valueChanged) segmentedControl.sendActions(for: .valueChanged) // then XCTAssertEqual( mockUserDefaults.gameStyleChanged, 1, "gameStyle user default wasn't changed")}

L’assertion when est que le drapeau gameStyleChanged est 0 avant que la méthode de test change le contrôle segmenté. Donc, si l’assertion then est également vraie, cela signifie que set(_:forKey:) a été appelé exactement une fois.

Exécutez le test ; il devrait réussir.

Tests de l’interface utilisateur dans Xcode

Les tests de l’interface utilisateur vous permettent de tester les interactions avec l’interface utilisateur. Le test UI fonctionne en trouvant les objets UI d’une app avec des requêtes, en synthétisant les événements, puis en envoyant les événements à ces objets. L’API vous permet d’examiner les propriétés et l’état d’un objet UI afin de les comparer à l’état attendu.

Dans le navigateur de test du projet BullsEye, ajoutez une nouvelle cible de test UI. Vérifiez que la cible à tester est BullsEye, puis acceptez le nom par défaut BullsEyeUITests.

Ouvrez BullsEyeUITests.swift et ajoutez cette propriété en haut de la classe BullsEyeUITests:

var app: XCUIApplication!

Dans setUp(), remplacez l’instruction XCUIApplication().launch() par la suivante:

app = XCUIApplication()app.launch()

Changez le nom de testExample() en testGameStyleSwitch().

Ouvrez une nouvelle ligne dans testGameStyleSwitch() et cliquez sur le bouton rouge Enregistrer en bas de la fenêtre de l’éditeur :

Cela ouvre l’app dans le simulateur dans un mode qui enregistre vos interactions comme des commandes de test. Une fois l’app chargée, touchez le segment de glissement du commutateur de style de jeu et l’étiquette supérieure. Ensuite, cliquez sur le bouton Xcode Record pour arrêter l’enregistrement.

Vous avez maintenant les trois lignes suivantes dans testGameStyleSwitch():

let app = XCUIApplication()app.buttons.tap()app.staticTexts.tap()

L’enregistreur a créé du code pour tester les mêmes actions que vous avez testées dans l’app. Envoyez un tap sur le curseur et l’étiquette. Vous les utiliserez comme base pour créer votre propre test UI.
Si vous voyez d’autres déclarations, supprimez-les simplement.

La première ligne duplique la propriété que vous avez créée dans setUp(), donc supprimez cette ligne. Vous n’avez pas encore besoin de taper quoi que ce soit, donc supprimez également .tap() à la fin des lignes 2 et 3. Maintenant, ouvrez le petit menu à côté de et sélectionnez segmentedControls.buttons.

Ce qui vous reste devrait être le suivant :

app.segmentedControls.buttonsapp.staticTexts

Tapez sur tout autre objet pour laisser l’enregistreur vous aider à trouver le code auquel vous pouvez accéder dans vos tests. Maintenant, remplacez ces lignes par ce code pour créer une section donnée:

// givenlet slideButton = app.segmentedControls.buttonslet typeButton = app.segmentedControls.buttonslet slideLabel = app.staticTextslet typeLabel = app.staticTexts

Maintenant que vous avez des noms pour les deux boutons du contrôle segmenté, et les deux étiquettes supérieures possibles, ajoutez le code suivant ci-dessous:

// thenif slideButton.isSelected { XCTAssertTrue(slideLabel.exists) XCTAssertFalse(typeLabel.exists) typeButton.tap() XCTAssertTrue(typeLabel.exists) XCTAssertFalse(slideLabel.exists)} else if typeButton.isSelected { XCTAssertTrue(typeLabel.exists) XCTAssertFalse(slideLabel.exists) slideButton.tap() XCTAssertTrue(slideLabel.exists) XCTAssertFalse(typeLabel.exists)}

Ceci vérifie si l’étiquette correcte existe lorsque vous tap() sur chaque bouton du contrôle segmenté. Exécutez le test – toutes les assertions devraient réussir.

Test de performance

Dans la documentation d’Apple : Un test de performance prend un bloc de code que vous voulez évaluer et l’exécute dix fois, en recueillant le temps d’exécution moyen et l’écart type pour les exécutions. La moyenne de ces mesures individuelles forme une valeur pour l’exécution du test qui peut ensuite être comparée à une ligne de base pour évaluer le succès ou l’échec.

Il est très simple d’écrire un test de performance : Il suffit de placer le code que vous voulez mesurer dans la fermeture du measure().

Pour voir cela en action, rouvrez le projet HalfTunes et, dans HalfTunesFakeTests.swift, ajoutez le test suivant:

func test_StartDownload_Performance() { let track = Track( name: "Waterloo", artist: "ABBA", previewUrl: "http://a821.phobos.apple.com/us/r30/Music/d7/ba/ce/mzm.vsyjlsff.aac.p.m4a") measure { self.sut.startDownload(track) }}

Exécutez le test, puis cliquez sur l’icône qui apparaît à côté du début de la fermeture arrière measure() pour voir les statistiques.

Cliquez sur Set Baseline pour définir un temps de référence. Ensuite, exécutez à nouveau le test de performance et visualisez le résultat – il pourrait être meilleur ou pire que la ligne de base. Le bouton Modifier vous permet de réinitialiser la ligne de base à ce nouveau résultat.

Les lignes de base sont stockées par configuration d’appareil, de sorte que vous pouvez avoir le même test s’exécutant sur plusieurs appareils différents, et faire en sorte que chacun maintienne une ligne de base différente dépendant de la vitesse du processeur de la configuration spécifique, de la mémoire, etc.

A chaque fois que vous apportez des modifications à une application qui pourrait avoir un impact sur les performances de la méthode testée, exécutez à nouveau le test de performance pour voir comment il se compare à la ligne de base.

Couverture de code

L’outil de couverture de code vous indique quel code de l’app est réellement exécuté par vos tests, afin que vous sachiez quelles parties du code de l’app ne sont pas (encore) testées.

Pour activer la couverture de code, modifiez l’action Test du schéma et cochez la case Gather coverage for sous l’onglet Options :

Exécutez tous les tests (Commande-U), puis ouvrez le navigateur de rapports (Commande-9). Sélectionnez Couverture sous l’élément supérieur de cette liste:

Cliquez sur le triangle de divulgation pour voir la liste des fonctions et des fermetures dans SearchViewController.swift:

Défilez jusqu’à updateSearchResults(_:) pour voir que la couverture est de 87,9%.

Cliquez sur le bouton fléché de cette fonction pour ouvrir le fichier source de la fonction. Lorsque vous passez la souris sur les annotations de couverture dans la barre latérale droite, les sections de code s’affichent en vert ou en rouge :

Les annotations de couverture montrent combien de fois un test frappe chaque section de code ; les sections qui n’ont pas été appelées sont surlignées en rouge. Comme vous pouvez vous y attendre, la boucle for s’est exécutée 3 fois, mais rien dans les chemins d’erreur n’a été exécuté.

Pour augmenter la couverture de cette fonction, vous pourriez dupliquer abbaData.json, puis le modifier pour qu’il provoque les différentes erreurs. Par exemple, changez "results" en "result" pour un test qui frappe print("Results key not found in dictionary").

Couverture à 100 %?

À quel point devez-vous vous efforcer d’obtenir une couverture de code à 100 % ? Google « 100% unit test coverage » et vous trouverez une gamme d’arguments pour et contre cela, ainsi qu’un débat sur la définition même de « 100% coverage ». Les arguments contre disent que les derniers 10-15% ne valent pas l’effort. Les arguments pour disent que les derniers 10-15% sont les plus importants, parce qu’ils sont si difficiles à tester. Google « hard to unit test bad design » pour trouver des arguments convaincants selon lesquels un code non testable est un signe de problèmes de conception plus profonds.

Where to Go From Here?

Vous avez maintenant de bons outils à utiliser pour écrire des tests pour vos projets. J’espère que ce tutoriel sur les tests unitaires et les tests d’interface utilisateur iOS vous a donné la confiance nécessaire pour tester toutes les choses !

Vous pouvez télécharger la version complétée du projet en utilisant le bouton Télécharger les matériaux en haut ou en bas de ce tutoriel. Continuez à développer vos compétences en ajoutant des tests supplémentaires de votre cru.

Voici quelques ressources pour une étude plus approfondie :

  • Il existe plusieurs vidéos de la WWDC sur le sujet des tests. Deux bonnes vidéos de la WWDC17 sont : Engineering for Testability et Testing Tips & Tricks.
  • L’étape suivante est l’automatisation : L’intégration continue et la livraison continue. Commencez par l’automatisation du processus de test avec Xcode Server et xcodebuild d’Apple, et l’article sur la livraison continue de Wikipedia, qui s’appuie sur l’expertise de ThoughtWorks.
  • Si vous avez déjà une application mais que vous n’avez pas encore écrit de tests pour elle, vous pourriez vous référer à Working Effectively with Legacy Code de Michael Feathers, parce que le code sans tests est du code hérité !
  • Les archives d’applications types Quality Coding de Jon Reid sont excellentes pour en apprendre davantage sur le développement piloté par les tests.

raywenderlich.com Weekly

La lettre d’information raywenderlich.com est le moyen le plus simple de rester à jour sur tout ce que vous devez savoir en tant que développeur mobile.

Ayez un condensé hebdomadaire de nos tutoriels et de nos cours, et recevez en prime un cours approfondi gratuit par e-mail !

Note moyenne

4,7/5

Ajouter une note pour ce contenu

Se connecter pour ajouter une note

90 notes

.

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée.