Comment tester unitairement les méthodes privées en Swift

Téléchargez votre copie gratuite de
Le manuel manquant
pour le développement Swift

Le guide que j’aurais aimé avoir quand j’ai commencé

Rejoignez 20,000+ Développeurs apprenant le développement Swift

Téléchargez votre exemplaire gratuit

Tester unitairement un projet Swift est très différent de tester unitairement un projet écrit en Objective-C. Pour ceux qui sont habitués à la flexibilité du runtime Objective-C, ils peuvent avoir l’impression d’avoir les mains liées dans le dos.

Contrôle d’accès

Bien que le contrôle d’accès soit un ajout très apprécié qui présente de nombreux avantages, il peut compliquer les tests unitaires, surtout si vous êtes novice en la matière. Vous savez probablement que vous pouvez appliquer l’attribut testable à une déclaration d’importation dans une cible de test pour avoir accès aux entités qui sont déclarées comme internes.

import XCTest@testable import Notesclass NotesTests: XCTestCase { ...}

Bien que ce soit un ajout pratique, il ne vous donne pas accès aux entités privées dans une cible de test. Cela nous amène à la question du jour . Comment testez-vous les entités privées en unité ?

Mauvaise question

La réponse courte à cette question est simple. Vous ne pouvez pas accéder aux entités privées à partir d’un autre module et cela s’applique également aux cibles de test. C’est aussi simple que cela. C’est à cela que sert le contrôle d’accès.

Mais ce n’est pas la réponse à la question. Si vous demandez comment tester en unité des entités privées, alors vous posez la mauvaise question. Mais pourquoi cela ?

Pas de vos affaires

Pourquoi déclarez-vous une entité privée ? Quelle est votre motivation pour le faire ? Regardez l’exemple suivant.

import Foundationstruct AccountViewViewModel { // MARK: - Properties let account: Account // MARK: - Public Interface var subscriptionAsString: String { switch account.subscription { case .monthly: return "Monthly Subscription" case .yearly: return "Yearly Subscription" case .trial: return "Trial Subscription" } } var expiresAtAsString: String { // Parse Date let date = parse(date: account.expiresAt) // Initialize Date Formatter let dateFormatter = DateFormatter() // Configure Date Formatter dateFormatter.dateFormat = "YYYY, MMMM" // Convert Date to String return dateFormatter.string(from: date) } // MARK: - Private Interface private func parse(date dateAsString: String) -> Date { let dateFormat: String if dateAsString.contains("/") { dateFormat = "YYYY'/'MM'/'dd" } else { dateFormat = "YYYYMMdd" } // Initialize Date Formatter let dateFormatter = DateFormatter() // Configure Date Formatter dateFormatter.dateFormat = dateFormat if let date = dateFormatter.date(from: dateAsString) { print(date) return date } else { fatalError("Incompatible Date Format") } }}

Je voudrais tester en unité la structure AccountViewViewModel. Comme vous pouvez le voir, la structure AccountViewViewModel expose deux propriétés calculées internes et elle définit également une méthode privée. La propriété calculée expiresAtAsString délègue une partie de son travail à la méthode privée parse(date:). Tester les propriétés calculées internes est simple.

// MARK: - Tests for Subscription as Stringfunc testSubscriptionAsString_Monthly() { let account = Account(expiresAt: "20161225", subscription: .monthly) let accountViewViewModel = AccountViewViewModel(account: account) XCTAssertEqual(accountViewViewModel.subscriptionAsString, "Monthly Subscription")}func testSubscriptionAsString_Yearly() { let account = Account(expiresAt: "20161225", subscription: .yearly) let accountViewViewModel = AccountViewViewModel(account: account) XCTAssertEqual(accountViewViewModel.subscriptionAsString, "Yearly Subscription")}func testSubscriptionAsString_Trial() { let account = Account(expiresAt: "20161225", subscription: .trial) let accountViewViewModel = AccountViewViewModel(account: account) XCTAssertEqual(accountViewViewModel.subscriptionAsString, "Trial Subscription")}// MARK: - Tests for Expires at as Stringfunc testExpiresAtAsString_20161225() { let account = Account(expiresAt: "20161225", subscription: .trial) let accountViewViewModel = AccountViewViewModel(account: account) XCTAssertEqual(accountViewViewModel.expiresAtAsString, "2016, December")}

Mais comment tester la méthode privée ? Nous ne pouvons pas accéder à la méthode privée à partir de la cible de test. Mais pourquoi devrions-nous tester unitairement la méthode privée ? Nous l’avons marquée comme privée pour une raison. N’est-ce pas ? Et cela nous amène à la réponse à la question avec laquelle nous avons commencé. Nous ne testons pas les méthodes privées.

Tester unitairement l’interface publique

En testant unitairement l’interface publique de la structure AccountViewViewModel nous testons automatiquement ou implicitement unitairement l’interface privée de la structure. Vous avez la tâche de vous assurer que l’interface publique est testée en profondeur. Cela signifie que vous devez vous assurer que chaque chemin de code de la structure AccountViewViewModel est couvert par des tests unitaires. En d’autres termes, la suite de tests unitaires doit aboutir à une couverture complète du code. Cela inclut les entités publiques, internes et privées.

Si nous activons la couverture du code dans Xcode et que nous exécutons les tests unitaires de la struct AccountViewViewModel, nous pouvons voir que certains chemins de code ne sont pas exécutés.

Code Coverage in Xcode

Cela nous indique que les tests unitaires sont incomplets. Nous pouvons ignorer le chemin de code de l’erreur fatale. Je ne teste jamais les chemins de code unitaires qui entraînent une erreur fatale, mais cela dépend largement de la façon dont vous utilisez les erreurs fatales dans vos projets.

Nous pouvons augmenter la couverture de code pour la struct AccountViewViewModel en ajoutant un test unitaire supplémentaire.

func testExpiresAtAsString_20161225WithForwardSlashes() { let account = Account(expiresAt: "2016/12/25", subscription: .trial) let accountViewViewModel = AccountViewViewModel(account: account) XCTAssertEqual(accountViewViewModel.expiresAtAsString, "2016, December")}

Couverture de code dans Xcode

Implémentation et spécification

Il est important de comprendre que nous testons la spécification de la structure AccountViewViewModel. Nous ne testons pas son implémentation. Bien que cela puisse sembler similaire, c’est en fait très différent. Nous testons la fonctionnalité de la structure AccountViewViewModel. Nous ne sommes pas intéressés par la façon dont elle fait sa magie sous le capot.

La principale conclusion de cet article est que les entités privées n’ont pas besoin d’être testées de façon unitaire. Les tests unitaires sont une forme de test en boîte noire. Cela signifie que nous ne testons pas l’implémentation de la structure AccountViewViewModel, nous testons sa spécification.

Cela ne signifie pas que nous ne sommes pas intéressés par l’implémentation, cependant. Nous devons nous assurer que la suite de tests unitaires couvre chaque chemin de code de l’entité que nous testons. Les rapports de couverture de code sont inestimables pour accomplir cela.

Téléchargez votre copie gratuite de
The Missing Manual
for Swift Development

Le guide que j’aurais aimé avoir quand j’ai commencé

Rejoignez 20 000+ développeurs qui apprennent le développement Swift

Téléchargez votre copie gratuite

.

Laisser un commentaire

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