Introduction : pourquoi les tests sont la base d'une application Swift fiable
\n\nLe développement sous iOS et macOS exige une grande fiabilité du code. Une erreur dans la logique de l'application peut coûter l'expérience utilisateur, et dans les projets commerciaux, de l'argent. Les tests en Swift ne sont pas simplement une « bonne pratique », mais une étape obligatoire du développement. Ils permettent de détecter les bogues à un stade précoce, de simplifier le refactoring et d'améliorer la qualité du code.
\n\nDans cet article, nous aborderons trois niveaux de tests : unitaires, d'intégration et e2e (de bout en bout). Vous découvrirez leurs différences, quand les utiliser, et verrez des exemples concrets en Swift utilisant le framework XCTest.
1. Tests unitaires en Swift : les bases fondamentales
\n\nLes tests unitaires vérifient des modules individuels (fonctions, méthodes, classes) de manière isolée. Leur objectif principal est de s'assurer que chaque « unité » de code fonctionne correctement.
\n\nConfiguration de XCTest
\n\nXCTest est intégré à Xcode. Pour créer une cible de test :
\n- \n
- Ouvrez votre projet dans Xcode. \n
- Sélectionnez File → New → Target. \n
- Choisissez Unit Testing Bundle. \n
Après la création, vous obtiendrez un fichier nommé YourAppTests.swift.
Exemple de test unitaire
\n\nSupposons que nous ayons une fonction de calcul de remise :
\n\nfunc calculateDiscount(price: Double, discount: Double) -> Double {\n guard discount >= 0 && discount <= 100 else { return price }\n return price * (1 - discount / 100)\n}\n\n\nÉcrivons le test :
\n\nimport XCTest\n@testable import YourApp\n\nclass DiscountTests: XCTestCase {\n func testCalculateDiscount_ValidInput() {\n let result = calculateDiscount(price: 1000, discount: 20)\n XCTAssertEqual(result, 800, accuracy: 0.01)\n }\n \n func testCalculateDiscount_InvalidDiscount() {\n let result = calculateDiscount(price: 500, discount: 150)\n XCTAssertEqual(result, 500)\n }\n}\n\n\nBonnes pratiques pour les tests unitaires :
\n- \n
- Un test = une vérification. \n
- Utilisez
setUp()pour initialiser les objets communs. \n - Évitez les dépendances réseau, base de données ou UI — utilisez des mocks. \n
2. Tests d'intégration : vérification des interactions entre composants
\n\nLes tests d'intégration vérifient comment différents modules fonctionnent ensemble. Par exemple, l'interaction d'un modèle de données avec la couche réseau ou la base de données CoreData.
\n\nExemple : test d'une requête réseau
\n\nCréons un service simple :
\n\nclass NetworkService {\n func fetchUser(completion: @escaping (Result<User, Error>) -> Void) {\n // Requête réelle vers l'API\n }\n}\n\n\nUn test d'intégration peut vérifier que le service traite correctement la réponse d'un serveur réel (ou d'un serveur mock local) :
\n\nclass NetworkServiceIntegrationTests: XCTestCase {\n var service: NetworkService!\n \n override func setUp() {\n super.setUp()\n service = NetworkService()\n }\n \n func testFetchUser_ReturnsUser() {\n let expectation = self.expectation(description: "Récupération utilisateur")\n \n service.fetchUser { result in\n switch result {\n case .success(let user):\n XCTAssertNotNil(user.name)\n case .failure:\n XCTFail("La requête a échoué")\n }\n expectation.fulfill()\n }\n \n waitForExpectations(timeout: 5)\n }\n}\n\n\nImportant : Les tests d'intégration sont plus lents que les tests unitaires, donc ils sont exécutés moins souvent (par exemple, avant un merge dans main).
\n\n3. Tests E2E (de bout en bout) avec XCUITest
\n\nLes tests E2E (End-to-End) simulent les actions de l'utilisateur : clics sur les boutons, saisie de texte, vérification de l'affichage de l'interface utilisateur. En Swift, pour cela