Dossier d’ingénierie

Créer des tests reproductibles pour les achats iOS avec StoreKit

Créer des tests reproductibles pour les achats iOS avec StoreKit

Les états les plus difficiles à reproduire dans une machine à états d’achats intégrés ne sont généralement pas les achats réussis, mais les annulations, les transactions en attente, les remboursements et les transactions précédentes restées inachevées. Confier directement ces branches à un environnement de transaction externe introduit dans les résultats les aléas du réseau, la configuration des produits et l’état du compte de test. Une méthode plus fiable consiste à définir les produits sur un Mac cloud à l’aide d’un fichier de configuration StoreKit, puis à contrôler les conditions de transaction avec SKTestSession afin de transformer la logique cliente en tests de régression reproductibles.

Définir les limites des tests hors ligne

Une configuration StoreKit permet de valider la correspondance des identifiants de produit, les transitions d’état des achats, l’actualisation des droits, les messages d’erreur et la logique de finalisation des transactions. Elle ne nécessite aucun accès à un parcours de paiement externe et facilite la reproduction des échecs.

Elle ne permet toutefois pas de confirmer que les produits en production sont correctement configurés, ni de couvrir les notifications serveur, la validation de reçus réels ou l’interface de paiement finale. Il est recommandé de répartir les tests en trois niveaux :

Niveau Objectif principal Fréquence d’exécution
Tests unitaires Calcul des droits et correspondance des états À chaque commit
Tests d’intégration StoreKit Achats, annulations, remboursements et transactions inachevées À chaque fusion
Recette dans un environnement externe Configuration des produits, notifications et parcours des reçus Avant la publication

Les tests hors ligne servent à raccourcir la boucle de retour, pas à masquer tous les risques liés aux transactions derrière une coche verte.

Fixer les produits et les points d’entrée des tests

Dans Xcode, créez un StoreKit Configuration File et ne conservez que les produits nécessaires aux tests. Les identifiants de produit doivent correspondre aux constantes du code. Les prix doivent servir uniquement aux tests d’interface ; la logique métier ne doit pas déterminer les droits à partir de chaînes de prix localisées.

Ajoutez le fichier de configuration au Scheme de test et créez un plan de test dédié, par exemple StoreKitRegression.xctestplan. Ne réutilisez pas le Scheme employé au quotidien par les développeurs, car une modification manuelle pourrait changer silencieusement les conditions de la CI.

La cible de test peut recréer la session dans setUpWithError() :

import StoreKitTest
import XCTest

final class PurchaseRegressionTests: XCTestCase {
    private var session: SKTestSession!

    override func setUpWithError() throws {
        session = try SKTestSession(configurationFileNamed: "Products")
        session.disableDialogs = true
        session.clearTransactions()
        session.failTransactionsEnabled = false
    }

    override func tearDownWithError() throws {
        session.clearTransactions()
        session = nil
    }
}

Indiquez le nom du fichier de configuration sans son extension. Si l’initialisation échoue, vérifiez d’abord que le fichier appartient à la cible de test et que le Scheme utilise bien la même configuration.

Séparer les branches de transaction en scénarios indépendants

Ne testez pas successivement la réussite, le remboursement et l’annulation dans un seul scénario très long. Plus un test comporte d’états, plus il devient difficile de déterminer, après un échec, à quelle étape la contamination s’est produite. Séparez au minimum les scénarios suivants :

Achat réussi et restauration

Vérifiez que les droits sont actualisés immédiatement après l’achat et qu’ils restent disponibles après la recréation de l’objet de la couche métier. Avant la fin du test, assurez-vous que l’application a traité et finalisé la transaction afin que le scénario suivant ne reçoive pas une ancienne mise à jour.

Annulation et injection d’échecs

Une annulation ne doit ni afficher un message indiquant que le paiement a échoué, ni enregistrer un état déverrouillé. Les erreurs réseau ou les erreurs de transaction génériques doivent laisser la possibilité de réessayer. Après avoir injecté une erreur au moyen des propriétés de la session, rétablissez les valeurs par défaut avant la fin du test en cours, sans compter sur le scénario suivant pour effectuer le nettoyage.

Remboursements et transactions inachevées

Après un remboursement, révoquez le droit correspondant sans supprimer par erreur d’autres produits encore valides. Pour les transactions inachevées, vérifiez que leur traitement reprend après le redémarrage de l’application au lieu de contrôler uniquement le rappel déclenché par un bouton.

Il est recommandé d’exécuter en série les tests qui modifient l’état d’un même produit. Des tests parallèles partageant un simulateur et une configuration peuvent facilement effacer leurs transactions respectives et provoquer des échecs intermittents qui n’apparaissent que dans la CI.

Fixer les conditions d’exécution en ligne de commande

Commencez par consulter les simulateurs disponibles, puis stockez le nom de l’appareil, la version du système et le chemin de Xcode dans des variables de CI. Ne laissez pas xcodebuild sélectionner automatiquement une destination quelconque.

set -euo pipefail

export DEVELOPER_DIR="/Applications/Xcode.app/Contents/Developer"
RESULT_DIR="$PWD/TestResults"
rm -rf "$RESULT_DIR"
mkdir -p "$RESULT_DIR"

xcodebuild test \
  -workspace App.xcworkspace \
  -scheme App-StoreKitTests \
  -testPlan StoreKitRegression \
  -destination 'platform=iOS Simulator,name=iPhone 16' \
  -resultBundlePath "$RESULT_DIR/StoreKit.xcresult"

Si plusieurs pipelines s’exécutent simultanément sur OakVM, attribuez à chaque tâche son propre répertoire de travail et son propre simulateur. Ne partagez ni DerivedData, ni répertoire de résultats, ni appareil déjà démarré. Vous pouvez exécuter xcrun simctl shutdown all avant le démarrage, mais cette commande affecte les autres tâches du même nœud. Elle ne convient donc que lorsque chaque nœud est réservé à une seule tâche.

Archiver les preuves et définir les seuils d’échec

Après l’échec d’un test, conserver uniquement les dernières dizaines de lignes de la console est généralement insuffisant. Archivez le fichier xcresult, le hash du commit, la somme de contrôle du fichier de configuration StoreKit, la version de Xcode et la version d’exécution du simulateur. Toute modification du fichier de configuration doit également passer par une revue de code afin d’éviter la suppression accidentelle d’un identifiant de produit.

Commencez par enregistrer un résumé de l’environnement :

xcodebuild -version
xcrun simctl list runtimes
shasum -a 256 Tests/StoreKit/Products.storekit

La liste de contrôle doit inclure les points suivants :

  • Chaque test crée-t-il et nettoie-t-il sa propre SKTestSession ?
  • Les boîtes de dialogue automatiques sont-elles désactivées afin d’éviter de bloquer les tâches sans surveillance ?
  • Les scénarios d’annulation, d’échec et de remboursement vérifient-ils séparément l’état de l’interface et celui des droits ?
  • Les tests dépendent-ils de leur ordre d’exécution ou d’une transaction précédente ?
  • Le fichier xcresult est-il toujours téléversé après un échec ?
  • Les journaux excluent-ils les identifiants sensibles et les données de transaction complètes ?

Une fois ces conditions fixées, les tests de régression des achats intégrés ne dépendent plus de manipulations manuelles. L’environnement externe ne prend en charge que les éléments qui nécessitent réellement une validation externe, tandis que des tests rapides, isolés et traçables protègent la machine à états côté client à chaque commit courant.

Questions fréquentes

Les tests StoreKit hors ligne remplacent-ils une validation dans un environnement de transaction réel ?

Non. Ils valident les états du client et les branches d’erreur, mais la configuration réelle des produits, les notifications serveur, les reçus et l’interface de paiement doivent être testés séparément.

Pourquoi les tests d’achat échouent-ils parfois uniquement dans la suite complète ?

Une transaction d’un test précédent peut subsister. Créez une nouvelle SKTestSession pour chaque test, effacez les transactions et exécutez en série les scénarios qui modifient le même produit.

Quels éléments faut-il archiver après un échec dans la CI ?

Conservez le bundle xcresult, les journaux, la révision de la configuration StoreKit, le hash du commit, la version de Xcode et celle du runtime du simulateur.

OAKVM BUILD NODE

Lancez votre prochain build sur un nœud physique dédié

Choisissez OakVM M4 ou OakVM M4 Pro et louez un Mac cloud non virtualisé à la journée, à la semaine, au mois ou au trimestre. La disponibilité réelle du nœud est indiquée en temps réel dans la console.

Choisir une configuration et louer