Les pannes les plus difficiles à reproduire dans un module d’achats intégrés ne sont généralement pas des boutons qui ne répondent plus, mais des états de transaction qui fuient d’un cas de test à l’autre : le premier test finalise un achat, puis le second, censé vérifier l’interface avant achat, retrouve directement les fonctionnalités déverrouillées. Pour exécuter durablement des tests de régression sur un Mac cloud, il faut isoler à la fois le simulateur, la session StoreKit et les données persistantes de l’application. Sinon, les tests réussiront individuellement mais échoueront régulièrement lorsqu’ils seront lancés en suite complète.
Commencer par définir trois niveaux de test
Le premier niveau couvre uniquement la machine à états métier. Il fournit les états « non acheté », « en cours de traitement », « acheté », « remboursé » et « expiré », puis vérifie les droits fonctionnels et l’état de l’interface sans déclencher de véritable transaction. Le deuxième niveau utilise StoreKit Testing pour valider la recherche des produits, les callbacks d’achat, l’écoute des transactions et les changements d’abonnement. Le troisième niveau correspond aux tests d’acceptation contrôlés avant publication, destinés à vérifier les notifications côté serveur et la configuration des produits en production.
Cette séparation permet d’exécuter localement la plupart des régressions et de déterminer clairement si une panne provient de la logique métier, de l’intégration des transactions côté client ou d’une configuration externe. Tous les tests d’interface ne doivent pas commencer par un clic sur le bouton d’achat. Les tests unitaires de la machine à états doivent rester majoritaires, tandis que les tests d’intégration StoreKit ne couvrent que les parcours critiques.
StoreKit Testing fournit un environnement de transaction contrôlable, et non un substitut au circuit de transaction en production. La réussite des tests indique que le client se comporte correctement face aux événements définis, mais ne prouve pas que la configuration externe a été validée.
Créer un catalogue de produits vérifiable
Créez StoreKit/Local.storekit dans le projet. Les identifiants de produit doivent être strictement identiques à ceux utilisés dans le code. Il est recommandé de les centraliser afin d’éviter leur dispersion dans les vues et les tests :
enum ProductID {
static let proMonthly = "com.example.app.pro.monthly"
static let proYearly = "com.example.app.pro.yearly"
}
Le fichier de configuration doit être placé sous contrôle de version. Les modifications apportées aux prix de test, aux noms affichés et aux périodes d’abonnement doivent également faire l’objet d’une revue de code. Une erreur fréquente consiste à copier une ancienne configuration et à ne modifier que le nom affiché, sans changer l’identifiant du produit, ce qui conduit la recherche de produits à renvoyer un tableau vide.
Créez un Scheme dédié aux tests, par exemple StoreKitRegression, puis sélectionnez le même fichier .storekit dans les options Run et Test du Scheme. Ne dépendez pas du Scheme personnel d’un développeur : seul un Scheme partagé peut être détecté par xcodebuild.
| Élément à vérifier | Résultat attendu |
|---|---|
| Identifiant du produit | Correspondance exacte entre le fichier de configuration, le code et les assertions |
| Scheme | Marqué comme Shared et ajouté au dépôt |
| Groupe d’abonnements | Relations de montée et de descente en gamme clairement définies au sein du groupe |
| Localisation | Les assertions de test ne dépendent pas de textes d’affichage susceptibles de changer |
Repartir d’une session propre pour chaque test
Utilisez StoreKitTest pour créer une session de test. Dans setUp, désactivez les boîtes de dialogue système et effacez les transactions. À la fin de chaque méthode de test, supprimez également le cache des droits enregistré par l’application elle-même.
import XCTest
import StoreKitTest
final class PurchaseRegressionTests: XCTestCase {
private var session: SKTestSession!
override func setUpWithError() throws {
session = try SKTestSession(
configurationFileNamed: "Local.storekit"
)
session.disableDialogs = true
session.clearTransactions()
UserDefaults.standard.removeObject(forKey: "cachedEntitlements")
}
func testMonthlyPurchaseUnlocksPro() async throws {
try await session.buyProduct(
identifier: ProductID.proMonthly
)
let unlocked = await EntitlementStore.shared.refresh()
XCTAssertTrue(unlocked)
}
}
L’écouteur de transactions doit démarrer avant le déclenchement de l’achat. Si celui-ci se termine très rapidement, une tâche d’écoute lancée trop tard peut manquer la mise à jour et provoquer des expirations intermittentes du test. Les remboursements, révocations et expirations d’abonnement doivent eux aussi être déclenchés explicitement par la session. Vérifiez ensuite le résultat après l’actualisation des droits, au lieu de vous limiter au statut de réussite renvoyé par l’API d’achat.
Ne pas masquer les conditions de concurrence avec une attente fixe
Task.sleep ne fait que retarder l’échec et ne prouve pas que l’état a été mis à jour. Une approche plus fiable consiste à exposer un état observable dans le gestionnaire des droits, puis à attendre une condition explicite avec un délai d’expiration court. En cas d’expiration, les journaux doivent au minimum inclure l’identifiant du produit, les droits actuels, le nombre de transactions inachevées et le nom du test, sans jamais afficher de jetons ni d’identifiants complets.
Stabiliser le point d’entrée en ligne de commande
Commencez par vérifier les appareils de simulation installés sur le Mac cloud, puis configurez la CI pour utiliser un nom fixe. Si le nom de l’appareil diffère dans l’image utilisée, adaptez les paramètres du pipeline au lieu de laisser le script sélectionner silencieusement un appareil quelconque.
xcrun simctl list devices available
xcodebuild test \
-workspace Example.xcworkspace \
-scheme StoreKitRegression \
-destination 'platform=iOS Simulator,name=iPhone 16 Pro' \
-resultBundlePath Artifacts/StoreKitTests.xcresult \
CODE_SIGNING_ALLOWED=NO
Lorsque seuls des tests sur simulateur sont exécutés, désactivez la signature afin de réduire les échecs sans rapport avec la logique de transaction. Le répertoire des résultats doit être vidé avant chaque exécution ou séparé par numéro de tâche. Dans le cas contraire, la présence d’un fichier xcresult existant provoquera immédiatement l’échec de la commande.
Les tests d’intégration StoreKit sont généralement plus stables lorsqu’ils s’exécutent en série. Si une exécution parallèle est nécessaire, chaque unité d’exécution doit disposer de son propre simulateur, de son propre répertoire Derived Data et de son propre répertoire de résultats. Lorsque plusieurs processus utilisent simultanément le même simulateur, leurs files de transactions et leurs données applicatives se contaminent mutuellement.
Concevoir les échecs comme des preuves exploitables
Couvrez au minimum six parcours : premier achat, annulation par l’utilisateur, transaction en attente, remboursement, renouvellement de l’abonnement et expiration de l’abonnement. Pour chacun, ajoutez des assertions à trois niveaux : l’état renvoyé par StoreKit, l’état du gestionnaire des droits et l’état final de l’interface. Vérifier uniquement le changement de libellé d’un bouton ne permet pas de détecter une absence de mise à jour des droits en arrière-plan.
Avant de soumettre les modifications, procédez aux vérifications suivantes :
- Le fichier
.storekitet le Scheme partagé sont placés sous contrôle de version. - Les transactions et le cache local des droits sont effacés avant chaque cas de test.
- L’écouteur de transactions démarre avant l’action d’achat.
- Les tests ne dépendent ni d’une attente fixe ni de leur ordre d’exécution.
- Chaque tâche parallèle utilise un simulateur et un répertoire d’artefacts distincts.
- En cas d’échec, les fichiers
xcresult, les journaux de test et les pièces jointes de l’interface sont conservés. - Les notifications côté serveur et la configuration des produits font l’objet d’une vérification distincte avant la mise en production.
Pour exécuter ce type de charge sur HireVM, commencez par vérifier les configurations disponibles dans la console, puis planifiez les simulateurs selon le nombre de partitions de test. La stabilité des tests d’achats intégrés dépend généralement davantage de l’isolation des états que de la vitesse d’une compilation unique. Assurez-vous d’abord qu’un pipeline en série réussit de manière reproductible avant d’ajouter du parallélisme : le diagnostic des pannes sera nettement moins coûteux.
Questions fréquentes
StoreKit Testing remplace-t-il entièrement la validation des achats en production ?
Non. Il valide les identifiants produit, les transitions, l’interface et les erreurs, mais une vérification séparée reste nécessaire pour les notifications serveur, la configuration réelle et le parcours complet.
Pourquoi un test réussit-il seul mais échoue-t-il dans la suite complète ?
Une transaction, un renouvellement ou une donnée locale du test précédent reste souvent actif. Réinitialisez la session et l’application avant chaque cas, puis évitez le partage simultané d’un simulateur.
Peut-on paralléliser les tests StoreKit ?
Oui, à condition d’attribuer un simulateur et un répertoire de résultats distincts à chaque worker. Plusieurs processus ne doivent pas partager la même session StoreKit.
Exécutez votre prochaine tâche Mac à distance sur un nœud dédié
Choisissez parmi deux configurations disponibles selon l’ampleur de la tâche, cinq nœuds et des cycles à la journée, à la semaine, au mois ou au trimestre. Vérifiez l’intégralité de la configuration et le montant en USD avant de commander.