Skip to content

Commit ebf7d04

Browse files
committed
Add testing docs
1 parent b15d0a9 commit ebf7d04

File tree

3 files changed

+623
-0
lines changed

3 files changed

+623
-0
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ Demonstrates marker system with custom icons and click handling.
3939
- [Lifecycle Management](docs/LIFECYCLE.md) - How OpenMapView handles Android lifecycle events
4040
- [Maven Central Setup](docs/MAVEN_CENTRAL_SETUP.md) - Publishing configuration and release process
4141
- [GitHub Workflows](docs/GITHUB_WORKFLOWS.md) - CI/CD pipeline and workflow architecture
42+
- [Unit Testing](docs/TESTING_UNIT.md) - JVM unit tests with Robolectric for Android framework APIs
43+
- [Instrumented Testing](docs/TESTING_INSTRUMENTED.md) - On-device testing guide (not yet implemented)
4244

4345
## Getting Started
4446

docs/TESTING_INSTRUMENTED.md

Lines changed: 274 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,274 @@
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

Comments
 (0)