|
| 1 | +# Instrumented Testing |
| 2 | + |
| 3 | +[Back to README](../README.md) |
| 4 | + |
| 5 | +This document explains instrumented testing for OpenMapView. Currently, the project does not have instrumented tests, but this guide describes when and how to add them. |
| 6 | + |
| 7 | +## Overview |
| 8 | + |
| 9 | +**Instrumented tests** (also called **instrumentation tests** or **on-device tests**) run on an Android emulator or physical device. They provide access to real Android framework APIs and hardware. |
| 10 | + |
| 11 | +**Current Status:** OpenMapView has **no instrumented tests** yet. All testing is done via [unit tests with Robolectric](TESTING_UNIT.md). |
| 12 | + |
| 13 | +## Unit Tests vs Instrumented Tests |
| 14 | + |
| 15 | +| Aspect | Unit Tests (JVM) | Instrumented Tests (Android) | |
| 16 | +|--------|------------------|------------------------------| |
| 17 | +| **Location** | `src/test/kotlin/` | `src/androidTest/kotlin/` | |
| 18 | +| **Runs on** | Local JVM | Emulator or device | |
| 19 | +| **Speed** | Fast (milliseconds) | Slow (seconds to minutes) | |
| 20 | +| **Android APIs** | Mocked (Robolectric) | Real Android framework | |
| 21 | +| **Hardware access** | No | Yes (camera, GPS, sensors) | |
| 22 | +| **Use for** | Logic, calculations, caching | UI, rendering, integration | |
| 23 | + |
| 24 | +## When to Use Instrumented Tests |
| 25 | + |
| 26 | +Add instrumented tests when: |
| 27 | + |
| 28 | +- **Testing real rendering**: Verify actual bitmap output, canvas drawing |
| 29 | +- **Testing touch gestures**: Complex multi-touch interactions |
| 30 | +- **Testing hardware integration**: GPS, sensors, device-specific behavior |
| 31 | +- **Testing UI components**: Views, animations, layouts |
| 32 | +- **Integration testing**: Multiple components working together on real Android |
| 33 | +- **Performance testing**: Frame rate, memory usage on actual devices |
| 34 | + |
| 35 | +## When Unit Tests Are Sufficient |
| 36 | + |
| 37 | +Continue using unit tests (with Robolectric) for: |
| 38 | + |
| 39 | +- **Pure logic**: Projection math, coordinate calculations |
| 40 | +- **Data classes**: Marker, LatLng, TileCoordinate |
| 41 | +- **Caching logic**: TileCache operations |
| 42 | +- **Algorithm verification**: Viewport calculations |
| 43 | + |
| 44 | +## Adding Instrumented Tests |
| 45 | + |
| 46 | +### 1. Create Test Directory |
| 47 | + |
| 48 | +```bash |
| 49 | +mkdir -p openmapview/src/androidTest/kotlin/de/afarber/openmapview |
| 50 | +``` |
| 51 | + |
| 52 | +### 2. Add Dependencies |
| 53 | + |
| 54 | +Update `openmapview/build.gradle.kts`: |
| 55 | + |
| 56 | +```kotlin |
| 57 | +dependencies { |
| 58 | + // Existing dependencies... |
| 59 | + |
| 60 | + // Instrumented testing |
| 61 | + androidTestImplementation("androidx.test.ext:junit:1.1.5") |
| 62 | + androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1") |
| 63 | + androidTestImplementation("androidx.test:runner:1.5.2") |
| 64 | + androidTestImplementation("androidx.test:rules:1.5.0") |
| 65 | +} |
| 66 | +``` |
| 67 | + |
| 68 | +### 3. Example Instrumented Test |
| 69 | + |
| 70 | +Create `openmapview/src/androidTest/kotlin/de/afarber/openmapview/OpenMapViewInstrumentedTest.kt`: |
| 71 | + |
| 72 | +```kotlin |
| 73 | +package de.afarber.openmapview |
| 74 | + |
| 75 | +import android.content.Context |
| 76 | +import androidx.test.core.app.ApplicationProvider |
| 77 | +import androidx.test.ext.junit.runners.AndroidJUnit4 |
| 78 | +import org.junit.Assert.assertEquals |
| 79 | +import org.junit.Assert.assertNotNull |
| 80 | +import org.junit.Test |
| 81 | +import org.junit.runner.RunWith |
| 82 | + |
| 83 | +@RunWith(AndroidJUnit4::class) |
| 84 | +class OpenMapViewInstrumentedTest { |
| 85 | + @Test |
| 86 | + fun testOpenMapViewCreation() { |
| 87 | + val context = ApplicationProvider.getApplicationContext<Context>() |
| 88 | + val mapView = OpenMapView(context) |
| 89 | + |
| 90 | + assertNotNull(mapView) |
| 91 | + assertEquals(0, mapView.childCount) // FrameLayout with no children initially |
| 92 | + } |
| 93 | + |
| 94 | + @Test |
| 95 | + fun testBitmapRendering() { |
| 96 | + // Test actual bitmap rendering with real Android framework |
| 97 | + val bitmap = BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_RED) |
| 98 | + |
| 99 | + assertNotNull(bitmap) |
| 100 | + assertEquals(48, bitmap.width) |
| 101 | + assertEquals(72, bitmap.height) |
| 102 | + |
| 103 | + // Verify actual pixel colors (not possible with Robolectric) |
| 104 | + val centerPixel = bitmap.getPixel(24, 36) |
| 105 | + // Assert red-ish color (exact value depends on marker design) |
| 106 | + } |
| 107 | +} |
| 108 | +``` |
| 109 | + |
| 110 | +## Running Instrumented Tests |
| 111 | + |
| 112 | +### Prerequisites |
| 113 | + |
| 114 | +- Android emulator running or device connected |
| 115 | +- Enable USB debugging on physical device |
| 116 | + |
| 117 | +### Commands |
| 118 | + |
| 119 | +```bash |
| 120 | +# Run all instrumented tests |
| 121 | +./gradlew :openmapview:connectedAndroidTest |
| 122 | + |
| 123 | +# Run on specific device |
| 124 | +./gradlew :openmapview:connectedDebugAndroidTest |
| 125 | + |
| 126 | +# Run with ADB |
| 127 | +adb shell am instrument -w de.afarber.openmapview.test/androidx.test.runner.AndroidJUnitRunner |
| 128 | +``` |
| 129 | + |
| 130 | +### Verify Device Connection |
| 131 | + |
| 132 | +```bash |
| 133 | +adb devices |
| 134 | +# Should show connected emulator or device |
| 135 | +``` |
| 136 | + |
| 137 | +## Test Categories for OpenMapView |
| 138 | + |
| 139 | +### Recommended Instrumented Tests |
| 140 | + |
| 141 | +1. **Rendering Tests** |
| 142 | + - Verify tile rendering produces non-null bitmaps |
| 143 | + - Check marker icon pixel colors |
| 144 | + - Test canvas drawing operations |
| 145 | + |
| 146 | +2. **Touch Gesture Tests** |
| 147 | + - Pan gesture recognition |
| 148 | + - Pinch-to-zoom gesture |
| 149 | + - Double-tap zoom |
| 150 | + |
| 151 | +3. **Integration Tests** |
| 152 | + - Full map initialization |
| 153 | + - Tile downloading and caching |
| 154 | + - Marker addition and rendering |
| 155 | + |
| 156 | +4. **Performance Tests** |
| 157 | + - Measure frame rate during panning |
| 158 | + - Memory usage with many markers |
| 159 | + - Tile cache eviction behavior |
| 160 | + |
| 161 | +### Keep as Unit Tests |
| 162 | + |
| 163 | +1. **Projection Math** |
| 164 | + - `latLngToPixel()`, `pixelToLatLng()` |
| 165 | + - `latLngToTile()`, `tileToPixel()` |
| 166 | + - Coordinate transformations |
| 167 | + |
| 168 | +2. **Data Structures** |
| 169 | + - Marker equality and hashing |
| 170 | + - TileCoordinate validation |
| 171 | + - LatLng bounds checking |
| 172 | + |
| 173 | +3. **Cache Logic** |
| 174 | + - TileCache put/get operations |
| 175 | + - LRU eviction algorithm |
| 176 | + |
| 177 | +## CI Integration |
| 178 | + |
| 179 | +Instrumented tests require an emulator or device, making CI setup more complex. |
| 180 | + |
| 181 | +### GitHub Actions Example |
| 182 | + |
| 183 | +```yaml |
| 184 | +name: Instrumented Tests |
| 185 | + |
| 186 | +on: [push, pull_request] |
| 187 | + |
| 188 | +jobs: |
| 189 | + instrumented-test: |
| 190 | + runs-on: macos-latest # macOS for hardware acceleration |
| 191 | + steps: |
| 192 | + - uses: actions/checkout@v4 |
| 193 | + |
| 194 | + - name: Set up JDK 17 |
| 195 | + uses: actions/setup-java@v4 |
| 196 | + with: |
| 197 | + distribution: temurin |
| 198 | + java-version: 17 |
| 199 | + |
| 200 | + - name: Run instrumented tests |
| 201 | + uses: reactivecircus/android-emulator-runner@v2 |
| 202 | + with: |
| 203 | + api-level: 29 |
| 204 | + target: default |
| 205 | + arch: x86_64 |
| 206 | + script: ./gradlew :openmapview:connectedCheck |
| 207 | +``` |
| 208 | +
|
| 209 | +**Note:** Instrumented tests on CI are significantly slower and may require paid runners. |
| 210 | +
|
| 211 | +## Test Structure |
| 212 | +
|
| 213 | +Typical instrumented test structure: |
| 214 | +
|
| 215 | +``` |
| 216 | +openmapview/src/androidTest/kotlin/de/afarber/openmapview/ |
| 217 | +├── OpenMapViewTest.kt # View creation and basic functionality |
| 218 | +├── MapControllerRenderTest.kt # Rendering verification |
| 219 | +├── GestureHandlingTest.kt # Touch gestures |
| 220 | +└── MarkerIntegrationTest.kt # Marker display and interaction |
| 221 | +``` |
| 222 | + |
| 223 | +## Espresso UI Testing |
| 224 | + |
| 225 | +For testing UI interactions, use Espresso: |
| 226 | + |
| 227 | +```kotlin |
| 228 | +@RunWith(AndroidJUnit4::class) |
| 229 | +class MapInteractionTest { |
| 230 | + @get:Rule |
| 231 | + val activityRule = ActivityScenarioRule(MainActivity::class.java) |
| 232 | + |
| 233 | + @Test |
| 234 | + fun testMapPanning() { |
| 235 | + onView(withId(R.id.openMapView)) |
| 236 | + .perform(swipeLeft()) |
| 237 | + .check(matches(isDisplayed())) |
| 238 | + } |
| 239 | +} |
| 240 | +``` |
| 241 | + |
| 242 | +## Best Practices |
| 243 | + |
| 244 | +1. **Keep instrumented tests minimal** - They are slower and more resource-intensive |
| 245 | +2. **Test what Robolectric cannot** - Real rendering, hardware, complex UI |
| 246 | +3. **Use test flavors** - Separate test APKs from production code |
| 247 | +4. **Mock network calls** - Use MockWebServer for tile downloading tests |
| 248 | +5. **Clean up resources** - Close HTTP clients, clear caches after tests |
| 249 | + |
| 250 | +## Trade-offs |
| 251 | + |
| 252 | +| Approach | Pros | Cons | |
| 253 | +|----------|------|------| |
| 254 | +| **Unit + Robolectric** | Fast, no device needed, works in CI | Some Android APIs not fully supported | |
| 255 | +| **Instrumented** | Real Android, hardware access, accurate | Slow, requires device, CI complexity | |
| 256 | +| **Hybrid** | Best of both worlds | More test code to maintain | |
| 257 | + |
| 258 | +**Current OpenMapView approach:** Pure unit tests with Robolectric provide sufficient coverage for the current feature set. |
| 259 | + |
| 260 | +## Future Considerations |
| 261 | + |
| 262 | +Add instrumented tests when: |
| 263 | + |
| 264 | +- Users report device-specific rendering issues |
| 265 | +- Adding complex gesture handling |
| 266 | +- Implementing hardware-dependent features |
| 267 | +- Validating performance on low-end devices |
| 268 | + |
| 269 | +## References |
| 270 | + |
| 271 | +- [Android Testing Documentation](https://developer.android.com/training/testing/instrumented-tests) |
| 272 | +- [Espresso Testing Framework](https://developer.android.com/training/testing/espresso) |
| 273 | +- [AndroidX Test Library](https://developer.android.com/training/testing/set-up-project) |
| 274 | +- [Android Emulator Runner (CI)](https://github.com/ReactiveCircus/android-emulator-runner) |
0 commit comments