Tester les températures iOS sur un Mac distant : valeurs, écarts et affichage
Lorsqu’une application météo ou de capteurs affiche « hausse de cinquante degrés Fahrenheit » pour une hausse de dix degrés Celsius, le problème ne vient généralement pas du formatage : un écart a été converti comme une température absolue. Sur un Mac dans le cloud, un paquet de tests Swift autonome permet de fixer d’abord les règles de calcul, avant de les intégrer au projet iOS. Inutile de connecter un capteur ou de lancer le simulateur.
Distinguer les relevés des écarts dès le départ
La conversion d’une température absolue fait intervenir à la fois un facteur d’échelle et un décalage d’origine : zéro degré Celsius correspond à trente-deux degrés Fahrenheit. Pour un écart de température, seul le facteur d’échelle compte : une hausse de dix degrés Celsius correspond à une hausse de dix-huit degrés Fahrenheit. Le type Measurement<UnitTemperature> de Foundation représente une température mesurée ; il ne déduit pas du nom d’une variable qu’il s’agit d’un écart.
Le modèle doit distinguer explicitement reading et delta, et la documentation des interfaces doit préciser les unités. Dans la couche métier, conservez les valeurs numériques sans formatage et ne choisissez le nombre de décimales qu’au moment de l’affichage. Ne reconvertissez pas un texte d’interface tel que « vingt-trois degrés et demi » en donnée d’entrée pour un calcul.
Un écart de température peut être négatif ; une température absolue inférieure au zéro absolu doit être rejetée. Ces deux champs ne doivent pas partager les mêmes règles de validation des plages de valeurs.
La validation ci-dessous concerne les températures physiques. Si le produit traite des données d’extrapolation mathématique, définissez une plage distincte plutôt que d’assouplir discrètement les contrôles d’entrée.
Créer un paquet de tests indépendant de l’interface
Commencez par vérifier la chaîne d’outils et créer le paquet dans un répertoire séparé, afin de ne pas modifier la configuration du projet existant. Ces commandes nécessitent des outils de ligne de commande Xcode installés et opérationnels. Les tests utilisent la chaîne d’outils Swift actuellement sélectionnée.
xcrun swift --version
workdir="$(mktemp -d)"
cd "$workdir"
swift package init --type library --name TemperatureChecks
Conservez les fichiers générés, puis enregistrez le code de la section suivante dans Tests/TemperatureChecksTests/TemperatureContractTests.swift. Il dépend uniquement de Foundation et de XCTest : aucun paquet externe n’est nécessaire.
Ce paquet autonome vérifie les règles de conversion, pas la conformité de l’affichage iOS. Une fois les tests intégrés au projet, faites-les appeler l’implémentation de production pour éviter que deux copies des fonctions évoluent séparément et que les tests continuent de vérifier une ancienne version.
Fixer les règles avec des points de référence et des conversions aller-retour
Pour une température absolue, vérifiez d’abord que la valeur est finie, puis convertissez-la en kelvins pour contrôler la limite physique inférieure. Pour un écart, éliminez le décalage en soustrayant « l’origine convertie » de « l’extrémité convertie ». Cette méthode s’applique uniquement aux conversions linéaires de température présentées ici ; ne la généralisez pas directement à n’importe quel modèle d’unités.
import Foundation
import XCTest
private enum TemperatureError: Error {
case invalidReading
}
private func kelvin(
_ value: Double,
from unit: UnitTemperature
) throws -> Double {
guard value.isFinite else {
throw TemperatureError.invalidReading
}
let result = Measurement(value: value, unit: unit)
.converted(to: .kelvin).value
guard result.isFinite, result >= 0 else {
throw TemperatureError.invalidReading
}
return result
}
private func temperatureDelta(
_ value: Double,
from source: UnitTemperature,
to target: UnitTemperature
) -> Double {
let endpoint = Measurement(value: value, unit: source)
.converted(to: target).value
let origin = Measurement(value: 0, unit: source)
.converted(to: target).value
return endpoint - origin
}
final class TemperatureContractTests: XCTestCase {
func testReferencePoints() throws {
let freezing = Measurement(
value: 0,
unit: UnitTemperature.celsius
).converted(to: .fahrenheit).value
XCTAssertEqual(freezing, 32, accuracy: 1e-9)
XCTAssertEqual(
try kelvin(0, from: .celsius),
273.15,
accuracy: 1e-9
)
XCTAssertEqual(
try kelvin(-273.15, from: .celsius),
0,
accuracy: 1e-9
)
}
func testTemperatureDeltas() {
for value in [-10.0, 0.0, 10.0] {
XCTAssertEqual(
temperatureDelta(
value,
from: .celsius,
to: .fahrenheit
),
value * 1.8,
accuracy: 1e-9
)
}
}
func testRoundTrip() {
for value in [-40.0, 0.0, 37.0, 100.0] {
let original = Measurement(
value: value,
unit: UnitTemperature.celsius
)
let restored = original.converted(to: .fahrenheit)
.converted(to: .celsius)
XCTAssertEqual(
restored.value,
value,
accuracy: 1e-9
)
}
}
func testInvalidReadings() {
for value in [
Double.nan,
Double.infinity,
-Double.infinity,
-274.0
] {
XCTAssertThrowsError(
try kelvin(value, from: .celsius)
)
}
}
}
Exécutez swift test à la racine du paquet. La tolérance sert à absorber les erreurs de calcul en virgule flottante ; ce n’est pas la marge d’erreur admise par le métier. La précision du capteur doit être définie séparément : augmenter la tolérance des assertions ne doit pas servir à masquer une erreur.
Les tests aller-retour ne remplacent pas non plus les points de référence : si les deux sens de conversion comportent des erreurs qui s’annulent, le test peut tout de même réussir. Conservez au minimum quatre catégories de cas : zéro, valeurs négatives, valeurs de référence connues et entrées invalides. Le point d’entrée des écarts de température doit également rejeter les valeurs non finies ; la fonction auxiliaire ci-dessus illustre uniquement la conversion, sans assurer la validation des entrées.
Valider séparément la localisation et la précision numérique
Enregistrez le code suivant dans format-temperature.swift, dans le répertoire du paquet, puis exécutez-le avec swift format-temperature.swift. L’unité est ici fixée au degré Celsius pour empêcher les préférences régionales de la modifier automatiquement. Le contrôle porte ainsi uniquement sur les règles d’affichage.
import Foundation
for identifier in ["zh_CN", "en_US", "fr_FR"] {
let locale = Locale(identifier: identifier)
let formatter = MeasurementFormatter()
formatter.locale = locale
formatter.unitOptions = .providedUnit
formatter.unitStyle = .short
formatter.numberFormatter.locale = locale
formatter.numberFormatter.minimumFractionDigits = 1
formatter.numberFormatter.maximumFractionDigits = 1
let reading = Measurement(
value: 23.45,
unit: UnitTemperature.celsius
)
print(identifier, formatter.string(from: reading))
}
Vérifiez le séparateur décimal, le symbole de l’unité et les espaces, sans supposer que la chaîne complète sera strictement identique d’une version du système à l’autre. Si vous utilisez des assertions par comparaison à un instantané, consignez aussi la version du système et l’identifiant régional.
Ne confiez pas implicitement l’affichage d’un écart à un formateur de températures absolues. Calculez d’abord l’écart dans l’unité cible, puis utilisez un formateur de nombres et des ressources localisées pour indiquer explicitement qu’il s’agit d’un « écart de température ». Le zéro négatif nécessite lui aussi une règle produit : si l’arrondi donne zéro, la suppression éventuelle du signe moins doit être gérée de manière cohérente dans la couche d’affichage.
Respecter trois frontières lors de l’intégration au projet
Premièrement, les données brutes doivent être accompagnées de leur unité. Une unité inconnue ne doit pas être interprétée par défaut comme des degrés Celsius, car une donnée erronée peut malgré tout sembler plausible. Une valeur d’énumération inconnue dans le protocole doit produire une erreur explicite.
Deuxièmement, les comparaisons aux seuils doivent porter sur des valeurs non arrondies, exprimées dans une même unité. Deux relevés affichant le même nombre ne déclenchent pas nécessairement la même alerte. Ajoutez un cas où « les valeurs diffèrent, mais l’affichage est identique » pour éviter qu’une refonte de l’interface ne modifie les décisions métier.
Troisièmement, la réussite des tests Foundation sur le Mac dans le cloud ne dispense pas de vérifier le formatage et la mise en page dans l’environnement iOS cible. Les widgets, les libellés des axes de graphiques et la lecture vocale doivent faire l’objet de validations distinctes ; vérifier uniquement l’écran principal ne suffit pas.
Conservez enfin les résultats des tests, la version de la chaîne d’outils et les sorties de formatage. En cas de régression, déterminez d’abord si elle touche la conversion, le contrat d’entrée ou l’affichage, puis corrigez la couche concernée. Une seule assertion sur une chaîne d’interface ne peut pas garantir à elle seule toute la justesse du traitement.
Questions fréquentes
Pourquoi ne pas convertir un écart comme une température absolue ?
La conversion absolue inclut un décalage entre les origines des échelles. Convertissez la valeur de l’écart et le zéro de l’unité source, puis soustrayez. Un écart de dix degrés Celsius vaut dix-huit degrés Fahrenheit.
Faut-il comparer le texte localisé à une chaîne exacte ?
Seulement si la version du système, la locale, l’unité et la précision sont fixées. Sinon, séparez les assertions numériques des contrôles d’arrondi, de symbole et de mise en page.
Confiez votre prochain build à un Mac dédié dans le cloud
MacFTP propose des machines physiques dédiées Apple Silicon exécutant macOS en natif, avec des locations à la journée, à la semaine, au mois ou au trimestre. Vérifiez la configuration, puis connectez votre chaîne d’outils au nœud ; le statut réel de disponibilité est indiqué en temps réel dans la console.