雲端 Mac 上的 iOS 地理距離與地理圍欄邊界測試

雲端 Mac 上的 iOS 地理距離與地理圍欄邊界測試

雲端 Mac 上的 iOS 地理距離與地理圍欄邊界測試

附近門市突然顯示在數萬公里外,不一定是定位服務出了問題。經緯度順序顛倒、跨越反經線時直接相減,或是先將距離四捨五入再判斷是否在範圍內,都可能造成類似異常。在 GNUMac 雲端 Mac 上,可以先用固定座標驗證計算層,再接回定位輸入與裝置回呼。如此一來,測試失敗時就能明確判斷問題出在哪一層,不必反覆拖曳地圖猜原因。

先訂清楚座標與距離的契約

本文驗證的是「兩個座標之間的地表距離」,不是開車路線的長度,也不代表使用者實際步行的距離。輸入採用十進位角度,順序為緯度在前、經度在後;內部距離一律以公尺計算,只有在介面顯示時才換算單位。

緯度必須介於負九十度至正九十度,經度必須介於負一百八十度至正一百八十度,同時也要拒絕非有限數值。不要默默把無效輸入截限到邊界,否則上游損壞的資料就會變成一筆看似正常的紀錄。

座標有效,不代表定位資料可信。緯度零度、經度零度是有效的位置,不能一律視為「尚未取得定位」;是否接受這個位置,應由資料來源的契約決定。

地圖上的畫面距離也不能當成預期值。投影方式、縮放比例與裝置像素都會改變畫面呈現,卻不應影響業務邏輯的計算。

在雲端 Mac 執行固定測試資料

準備好已選定開發工具鏈的 macOS 環境,執行 xcrun swift --version,確認編譯器可用。將以下內容儲存為 distance-check.swift。這支指令稿不會讀取目前位置,不需要請求定位權限,也不會存取網路。

import Foundation
import CoreLocation

enum CoordinateError: Error {
    case invalid
}

func point(_ latitude: Double, _ longitude: Double) throws -> CLLocation {
    let coordinate = CLLocationCoordinate2D(
        latitude: latitude,
        longitude: longitude
    )
    guard latitude.isFinite,
          longitude.isFinite,
          CLLocationCoordinate2DIsValid(coordinate) else {
        throw CoordinateError.invalid
    }
    return CLLocation(latitude: latitude, longitude: longitude)
}

func rejects(_ latitude: Double, _ longitude: Double) -> Bool {
    do {
        _ = try point(latitude, longitude)
        return false
    } catch {
        return true
    }
}

let origin = try point(0, 0)
precondition(origin.distance(from: origin) == 0)

let east = try point(0, 179.9)
let west = try point(0, -179.9)
let crossing = east.distance(from: west)
precondition(crossing > 20_000 && crossing < 23_000)
precondition(abs(crossing - west.distance(from: east)) < 1)

let polarA = try point(89.9, 0)
let polarB = try point(89.9, 180)
let polarDistance = polarA.distance(from: polarB)
precondition(polarDistance > 20_000 && polarDistance < 23_000)

precondition(rejects(91, 0))
precondition(rejects(0, 181))
precondition(rejects(.nan, 0))
precondition(rejects(0, .infinity))

執行指令如下。所有斷言都通過時不會有任何輸出;若有失敗,則會以非零狀態碼結束,適合直接作為管線中的一個步驟。

xcrun swift distance-check.swift

這些範圍是為固定測試資料設定的寬鬆容差,不是效能實測結果,也不是對業務精度的承諾。反經線兩側的點其實相距很近;在極區,即使經度差很大,也不代表相隔半個地球。這兩項測試都能揪出直接將經緯度當成平面座標相減的錯誤。

將圍欄邊界判斷獨立為純函式

假設業務規則是「距離小於或等於半徑,就算進入」。先用人為設定的距離測試比較函式,再接入座標計算結果,避免同時除錯兩個層次。將以下程式碼附加到同一個檔案後,重新執行。

func isInside(distance: Double, radius: Double) -> Bool {
    guard distance.isFinite, radius.isFinite,
          distance >= 0, radius >= 0 else {
        return false
    }
    return distance <= radius
}

precondition(isInside(distance: 499.9, radius: 500))
precondition(isInside(distance: 500, radius: 500))
precondition(!isInside(distance: 500.1, radius: 500))
precondition(!isInside(distance: .nan, radius: 500))
precondition(!isInside(distance: 0, radius: -1))

let meters = 1_250.0
precondition(meters / 1_000 == 1.25)

邊界測試資料必須包含恰好相等的情況,而不只是明顯在範圍內或範圍外的案例。不要從顯示為「零點五公里」的字串反推判斷結果;不同的原始距離,可能會四捨五入成相同的文字。

如果產品需要減少邊界附近反覆切換的情況,可以定義遲滯策略,讓進入與離開使用不同門檻。不過,這屬於狀態機的邏輯,需要另外測試連續的輸入序列,不能在比較函式裡隨意加上一個代表「差不多」的常數。

定位精度不等於計算容差

真實的定位樣本還包含時間與水平精度。資料接入層應明確拒絕過期樣本,並處理水平精度為負值的情況;即使座標有效,只要精度較差,也不應直接產生「確認進入」事件。

一種便於稽核的保守規則是:設距離為 d、水平精度為 a、半徑為 r。只有 d+a≤r 才判定為確定在內;只有 d−a>r 才判定為確定在外,其餘情況都標記為不確定。這是業務上的保守判斷,並不是將精度值解讀為絕對誤差的保證。

測試時,分別注入精確、模糊、過期與無效的樣本,確認是否保留不確定狀態,以及介面是否錯誤地顯示確定結論。不要放寬計算斷言來掩蓋定位雜訊。

保留驗證紀錄並接回 iOS 專案

上述指令稿適合快速複核框架行為。正式整合時,應將座標驗證與比較函式放進可測試的模組,讓測試目標直接呼叫正式實作;只維護一份演算法,避免指令稿正確,應用程式卻仍在使用舊邏輯。

每次執行都應保留提交識別碼、工具鏈版本、固定測試座標、原始公尺數值,以及失敗的斷言。系統升級後,先確認失敗屬於輸入驗證、距離範圍,還是狀態策略的問題,不要直接更新所有預期值。

最後仍要安排一輪 iOS 實機驗收:區域監測事件、背景執行與實際移動屬於另一條驗證鏈。純計算測試通過,只能說明固定輸入得到了符合契約的結果。將這項界線寫進驗收紀錄,比增加隨機座標更有價值。

常見問題

雲端 Mac 腳本通過,是否代表 iOS 地理圍欄已完成驗收?

不是。腳本驗證座標處理、距離計算與門檻策略;區域監測回呼、背景行為及實際定位誤差,仍須在 iOS 實機上另外驗證。

可以直接用畫面顯示的公里數判斷是否進入圍欄嗎?

不可以。業務判斷應使用未格式化的公尺值,顯示時才換算及四捨五入;定位精度的處理策略也應獨立定義。

為你的工作流程配置一台專屬 Mac mini

依照專案需求挑選機型、節點與租用期間。下單前先確認記憶體、儲存空間和連線需求;實際可用狀態以控制台即時回報為準。

先確認工作流程,再啟用你的設備。

專屬實體 Mac mini。選好機型、節點與租期後,再依照專案需求配置儲存空間。