클라우드 Mac에서 iOS 온도 변환 검증하기: 절대 온도, 온도 차이, 표시
날씨 앱이나 센서 앱에서 “섭씨 십 도 상승”이 “화씨 오십 도 상승”으로 표시된다면, 대개 서식 문제가 아니라 온도 차이를 절대 온도로 취급해 변환한 것이 원인입니다. 클라우드 Mac에서는 독립 Swift 테스트 패키지로 계산 계약을 먼저 확정한 뒤 iOS 프로젝트에 통합할 수 있습니다. 센서를 연결하거나 시뮬레이터를 실행할 필요도 없습니다.
측정값과 변화량부터 구분하기
절대 온도를 변환할 때는 배율과 영점 오프셋을 모두 적용합니다. 섭씨 영 도는 화씨 삼십이 도에 해당합니다. 반면 온도 차이에는 배율만 적용하므로, 섭씨 십 도 상승은 화씨 십팔 도 상승에 해당합니다. Foundation의 Measurement<UnitTemperature>는 온도 측정값을 표현하며, 변수 이름만 보고 변화량인지 판단하지 않습니다.
모델에서 reading과 delta를 명확히 구분하고 인터페이스 문서에 단위를 명시하는 것이 좋습니다. 비즈니스 로직 계층에서는 서식을 적용하지 않은 수치를 일관되게 유지하고, 표시할 때만 소수 자릿수를 결정하세요. “이십삼 점 오 도” 같은 화면 텍스트를 다시 파싱해 계산 입력으로 사용해서는 안 됩니다.
온도 차이는 음수일 수 있지만, 절대 영도보다 낮은 절대 온도는 거부해야 합니다. 두 필드에 동일한 범위 검증 규칙을 적용해서는 안 됩니다.
아래 검증은 물리적 온도를 대상으로 합니다. 제품에서 수학적 외삽 데이터를 다룬다면 입력 제한을 암묵적으로 완화하지 말고, 별도의 유효 범위를 명시해야 합니다.
UI에 의존하지 않는 테스트 패키지 만들기
기존 프로젝트 설정을 변경하지 않도록 별도 디렉터리에서 툴체인을 확인하고 패키지를 생성합니다. 명령을 실행하려면 사용 가능한 Xcode 명령줄 도구가 설치되어 있어야 합니다. 테스트에는 현재 선택된 Swift 툴체인이 사용됩니다.
xcrun swift --version
workdir="$(mktemp -d)"
cd "$workdir"
swift package init --type library --name TemperatureChecks
생성된 파일은 그대로 두고, 다음 절의 코드를 Tests/TemperatureChecksTests/TemperatureContractTests.swift로 저장합니다. 코드는 Foundation과 XCTest에만 의존하므로 외부 패키지를 추가할 필요가 없습니다.
독립 패키지에서 검증하는 것은 변환 규칙입니다. 이것만으로 iOS 표시까지 검수를 마쳤다고 볼 수는 없습니다. 프로젝트로 옮긴 뒤에는 테스트가 실제 서비스 코드를 호출하도록 해야 합니다. 함수 사본 두 개를 따로 수정하다가 테스트는 계속 이전 사본만 검사하는 상황을 피해야 합니다.
기준점과 왕복 변환으로 규칙 확정하기
절대 온도는 먼저 유한한 값인지 확인한 다음, 켈빈으로 변환해 물리적 하한을 검사합니다. 온도 차이는 “변환된 끝점에서 변환된 영점을 빼는” 방식으로 오프셋을 제거합니다. 이 방식은 여기서 다루는 온도의 선형 변환에만 사용하며, 임의의 단위 모델에 그대로 일반화해서는 안 됩니다.
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)
)
}
}
}
패키지 루트 디렉터리에서 swift test를 실행합니다. 허용 오차는 부동소수점 연산 오차를 흡수하기 위한 것이지, 비즈니스 요구사항에서 허용하는 오차가 아닙니다. 센서 정확도는 별도로 정의해야 하며, 단언문의 허용 오차를 늘려 오류를 감춰서는 안 됩니다.
왕복 변환 테스트도 기준점 검증을 대신할 수는 없습니다. 양방향 변환의 오류가 서로 상쇄되면 왕복 테스트는 통과할 수 있기 때문입니다. 최소한 영점, 음수, 알려진 참조값, 유효하지 않은 입력의 네 가지 유형을 유지하세요. 온도 차이 입력에서도 유한하지 않은 값은 별도로 거부해야 합니다. 위 보조 함수는 변환만 보여 주며 입력 검증은 담당하지 않습니다.
현지화 표시와 수치 정밀도를 따로 검수하기
아래 코드를 패키지 디렉터리의 format-temperature.swift로 저장한 뒤 swift format-temperature.swift로 실행합니다. 여기서는 섭씨 단위를 고정해 지역 설정에 따라 단위가 자동으로 바뀌지 않도록 합니다. 이렇게 하면 표시 규칙에만 집중해 검사할 수 있습니다.
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))
}
소수 구분자, 단위 기호, 간격을 확인하되 시스템 버전이 달라도 전체 문자열이 글자 단위로 완전히 같을 것이라고 가정하지 마세요. 스냅샷 단언이 필요하다면 시스템 버전과 로캘 식별자도 함께 기록합니다.
온도 차이 표시를 절대 온도 포매터의 암묵적인 처리에 맡기지 마세요. 먼저 대상 단위의 온도 차이를 계산한 뒤, 숫자 포매터와 현지화 리소스를 사용해 “온도 차이”임을 명확히 표시해야 합니다. 음의 영에도 제품 차원의 규칙이 필요합니다. 반올림 후 영으로 표시될 때 음수 부호를 제거할지 여부는 표시 계층에서 일관되게 처리해야 합니다.
프로젝트 통합 시 세 가지 경계 지키기
첫째, 원시 데이터에는 반드시 단위가 포함되어야 합니다. 알 수 없는 단위를 기본적으로 섭씨로 해석해서는 안 됩니다. 잘못된 데이터도 그럴듯한 범위 안에 들어올 수 있기 때문입니다. 통신 프로토콜에 정의되지 않은 열거형 값이 들어오면 명확한 오류를 반환해야 합니다.
둘째, 임곗값 비교는 단위를 통일하고 반올림하지 않은 수치로 수행해야 합니다. 화면에 같은 숫자로 표시되는 두 측정값이라도 경보 판정 결과는 다를 수 있습니다. “수치는 다르지만 표시는 같은” 테스트 사례를 추가하면 UI 리팩터링이 비즈니스 판단에 영향을 주는 것을 방지할 수 있습니다.
셋째, 클라우드 Mac에서 Foundation 테스트를 통과한 뒤에도 대상 iOS 실행 환경에서 서식과 레이아웃을 확인해야 합니다. 위젯, 차트 축 레이블, 음성 읽기는 각각 검수해야 하며, 메인 화면만 확인해서는 안 됩니다.
마지막으로 테스트 결과, 툴체인 버전, 서식이 적용된 출력을 보관하세요. 회귀 문제가 발생하면 먼저 변환, 입력 계약, 표시 중 어디에서 변화가 생겼는지 판단한 뒤 해당 계층을 수정해야 합니다. 화면 문자열 단언 하나로 모든 정확성을 검증하려 해서는 안 됩니다.
자주 묻는 질문
온도 차이를 절대 온도처럼 변환하면 안 되는 이유는 무엇인가요?
절대 온도 변환에는 척도의 원점 차이가 포함되기 때문입니다. 온도 차이 값과 원래 단위의 영점을 각각 변환한 뒤 빼면 됩니다. 섭씨 십 도의 온도 차이는 화씨 십팔 도의 온도 차이입니다.
지역화된 온도 문자열을 고정 문자열과 비교해도 되나요?
시스템 버전, 로케일, 단위와 정밀도를 모두 고정한 경우에만 권장합니다. 일반적인 검증에서는 수치 계약을 먼저 검사하고 표시 계층의 반올림, 단위와 레이아웃을 따로 확인하세요.
다음 빌드는 단독 클라우드 Mac에서
MacFTP는 기본 macOS를 실행하는 Apple Silicon 전용 물리 서버를 제공합니다. 일·주·월·분기 단위로 이용 기간을 선택하세요. 먼저 구성을 확인한 뒤 기존 도구 체인을 노드에 연결하면 됩니다. 실제 사용 가능 여부는 콘솔에 표시되는 실시간 상태를 기준으로 확인하세요.