Vérifier les règles de tri des listes iOS sur un Mac cloud
Une liste de fichiers contient « Rapport2 » et « Rapport10 » ; après une actualisation, deux éléments portant le même nom ont également échangé leur place. Le problème ne vient pas forcément du rafraîchissement de l’interface : le comparateur ne définit peut-être pas un ordre complet. Lorsque vous exécutez des tests iOS sur un Mac cloud, isoler le tri de la vue et valider les règles sur un petit jeu de données permet de reproduire le problème plus facilement qu’en formulant des hypothèses à partir d’une capture vidéo.
Définir d’abord le contrat de tri
Cet article applique les règles suivantes à une liste de fichiers : tri croissant par nom, comparaison des nombres dans leur ordre naturel, sans tenir compte de la casse ni des accents. Lorsque deux noms sont équivalents selon cette comparaison, les éléments sont départagés par ordre croissant de leur identifiant métier persistant. La locale de comparaison est fixée pour que les tests ne dépendent pas des réglages de la machine.
Ce n’est pas une réponse universelle. Un carnet d’adresses destiné aux utilisateurs peut nécessiter un tri adapté à la langue courante ; une liste de fichiers partagée entre plusieurs appareils peut, au contraire, exiger un ordre identique partout. Définissez le besoin avant de passer à l’implémentation. « Cela semble naturel » n’est pas un critère de validation.
L’identifiant stable doit rester le même avant et après une actualisation. L’indice dans le tableau, l’heure de réception et un nouvel UUID généré à chaque création ne conviennent pas pour départager les éléments portant le même nom. Il faut également rejeter les identifiants en double à l’entrée des données : sinon, même la dernière clé de tri ne permettra pas de distinguer les éléments.
Un tri stable préserve uniquement l’ordre relatif des éléments équivalents dans les données d’entrée. Il ne corrige pas un serveur qui renvoie ces éléments dans un ordre différent à chaque requête.
Isoler le comparateur dans une fonction pure
Ajoutez le fichier suivant à la cible de l’application. L’exemple utilise systématiquement en_US_POSIX : ce choix matérialise une politique de comparaison fixe, sans prétendre convenir à l’affichage des noms dans toutes les langues.
import Foundation
struct ListEntry {
let id: String
let name: String
}
func entryLess(_ lhs: ListEntry, _ rhs: ListEntry) -> Bool {
let order = lhs.name.compare(
rhs.name,
options: [.numeric, .caseInsensitive, .diacriticInsensitive],
range: nil,
locale: Locale(identifier: "en_US_POSIX")
)
if order != .orderedSame {
return order == .orderedAscending
}
return lhs.id < rhs.id
}
Pendant la comparaison, ne consultez pas l’état du réseau, ne modifiez pas de compteur et ne lisez pas de préférences utilisateur susceptibles de changer. Un même tri compare plusieurs fois les mêmes objets : les résultats doivent rester cohérents. N’implémentez pas non plus le comparateur avec une relation « inférieur ou égal » : un objet ne peut pas précéder lui-même.
Des noms équivalents à la comparaison ne désignent pas nécessairement le même objet métier. Avec la politique retenue, « Resume » et « résumé » peuvent appartenir au même groupe, mais cela ne justifie pas de fusionner les enregistrements. Le tri, la recherche et la déduplication relèvent de trois contrats distincts.
Verrouiller les comportements essentiels avec un petit jeu de données
Dans le fichier de tests unitaires, importez le module de l’application, puis ajoutez la classe de test. Placez ce code dans une cible de tests ayant accès aux types définis ci-dessus ; il suffit ensuite d’enrichir les exemples de ListSortingTests avec vos propres noms de fichiers.
import XCTest
final class ListSortingTests: XCTestCase {
let items = [
ListEntry(id: "d", name: "Report10"),
ListEntry(id: "b", name: "report2"),
ListEntry(id: "a", name: "REPORT2"),
ListEntry(id: "c", name: "Report1")
]
func testNaturalOrderAndTies() {
XCTAssertEqual(
items.sorted(by: entryLess).map(\.id),
["c", "a", "b", "d"]
)
}
func testComparatorProperties() {
for a in items {
XCTAssertFalse(entryLess(a, a))
for b in items {
if entryLess(a, b) {
XCTAssertFalse(entryLess(b, a))
}
for c in items {
if entryLess(a, b) && entryLess(b, c) {
XCTAssertTrue(entryLess(a, c))
}
}
}
}
}
func testInputOrderDoesNotChangeOutput() {
let expected = items.sorted(by: entryLess).map(\.id)
XCTAssertEqual(
Array(items.reversed()).sorted(by: entryLess).map(\.id),
expected
)
let rotated = Array(items.dropFirst()) + [items[0]]
XCTAssertEqual(
rotated.sorted(by: entryLess).map(\.id),
expected
)
}
}
Le premier test valide l’ordre des nombres et le départage des noms identiques. Le deuxième vérifie l’irréflexivité, l’asymétrie et la transitivité ; il peut détecter les contradictions introduites par un assemblage improvisé de plusieurs règles. Le troisième modifie l’ordre d’entrée pour vérifier que le résultat ne dépend pas de l’ordre de réception.
La triple boucle convient à une petite dizaine de cas représentatifs, voire un peu plus. Ne l’appliquez pas directement à toute la liste de production. Pour les grands jeux de données, mesurez séparément le temps de tri : une vérification des propriétés n’est pas un test de performance.
Couvrir les cas limites Unicode et les valeurs absentes
Ajoutez des noms vides, des nombres avec des zéros initiaux, des noms chinois, des accents combinants et des séquences d’emoji. Un caractère précomposé et une « lettre suivie d’un accent combinant » peuvent avoir le même aspect à l’écran ; la longueur de la chaîne ne permet pas de déterminer leur ordre d’affichage.
Pour « 文件02 » et « 文件2 », ne présumez pas du résultat de la comparaison numérique. Décidez d’abord si le produit doit les considérer comme appartenant au même groupe, puis vérifiez le comportement dans l’environnement d’exécution retenu. Si les zéros initiaux doivent être distingués, vous pouvez ajouter une règle secondaire explicite sur le nom lorsque la comparaison principale conclut à une équivalence. L’identifiant métier doit toujours rester le dernier critère de départage.
Traitez les noms absents de façon uniforme lors de la conversion du modèle : par exemple, en les remplaçant par une chaîne vide ou en définissant une branche qui place les éléments sans nom à la fin. N’attribuez pas arbitrairement de valeur par défaut dans le comparateur. Pour un tri décroissant, redéfinissez également le sens de comparaison des clés. Il ne suffit pas de nier le résultat du comparateur croissant : cette négation ferait considérer des objets équivalents comme « inférieurs ».
Exécuter les tests dans le cloud et conserver les éléments de diagnostic
Dans la tâche de test sur le Mac cloud, fixez la version de Xcode, l’environnement d’exécution du simulateur et la locale de comparaison, puis lancez ces tests avec le point d’entrée habituel des tests unitaires. Conservez l’identifiant du commit, la version de la chaîne d’outils, les données d’entrée qui ont provoqué l’échec et la séquence d’identifiants réellement obtenue en sortie. Il n’est pas nécessaire de consigner les vrais noms de fichiers des clients.
Validez d’abord la fonction pure, puis l’interface : effectuez deux actualisations consécutives et vérifiez que les éléments portant le même nom ne changent pas de place. Supprimez un élément, puis réinsérez-le, et vérifiez que l’ordre relatif des autres éléments respecte les règles. La couche d’interface doit réutiliser le même comparateur, sans implémenter un autre tri.
Fixer la locale ne garantit pas que les règles de classement du système resteront inchangées pour toujours. Après une mise à niveau de l’environnement d’exécution, revérifiez les exemples linguistiques représentatifs. Si les écarts proviennent des règles système, décidez s’il faut accepter le changement ou adopter des clés de tri personnalisées, plutôt que de mettre immédiatement à jour tous les instantanés.
Le livrable final doit inclure le contrat de tri, les exemples représentatifs, les vérifications des propriétés du comparateur et les résultats de validation des actualisations. Ainsi, au prochain changement d’ordre, vous pourrez déterminer si les données, les règles ou l’environnement d’exécution ont changé, au lieu de recommencer à formuler des hypothèses à partir de captures d’écran.
Questions fréquentes
Un tri stable suffit-il à garder les noms identiques dans le même ordre ?
Non. Il conserve leur ordre relatif dans les données d'entrée. Si le serveur change cet ordre, il faut un identifiant métier durable et unique comme dernier critère.
Une locale fixe garantit-elle un instantané de tri permanent ?
Non. Les règles de comparaison du système peuvent évoluer avec l'environnement d'exécution. Revérifiez les cas représentatifs et les propriétés du comparateur lors des mises à jour.
Louez un Mac cloud pour votre prochain build
Choisissez un modèle, une ville et une durée de location, puis utilisez un nœud physique dédié avec vos outils de développement. La disponibilité est indiquée en temps réel dans la console.