Testing and publishing
Test the JSON, schemes and probes of a sampling package, run it in an app, and publish it on pub.dev.
Tests matter more for a sampling package than for most code. A missing JSON registration or a wrong data type compiles and runs. It fails only at the next app start or when the data is uploaded.
Write the tests
The example package has three test files, with 23 tests in all:
| File | What it tests |
|---|---|
test/json_test.dart | A JSON round trip of each data class, the configuration class, a measurement and a protocol |
test/package_test.dart | The measure types, the default sampling schemes, the probes that the package creates and the privacy functions |
test/probes_test.dart | The data path of both probes, with the barometer of sensors_plus mocked |
Each test file runs in its own isolate, so each one initializes CARP Mobile Sensing (CAMS) and registers the package in setUpAll(). Initialize CAMS before the first use of SamplingPackageRegistry, as in the app. See Startup order.
Test the JSON round trips
Create test/json_test.dart, which round-trips each class that CAMS stores through a JSON string, as CAMS does when it saves and restores a study:
- Decode through the registry: decode with
FromJsonFactory().fromJson(), or withMeasurement.fromJson()for aMeasurement, never by callingfromJsonFunctionyourself. Only the registry,FromJsonFactory, fails when a class is not registered. The data test of CAMS calls the functions directly, so it does not notice that CAMS 2.5.1 never registers its ownAccelerationFeaturesdata class. - Check the JSON type:
__typemust equal the measure type. Data of another type is never uploaded and never reaches privacy functions or triggers. - Round-trip whole objects: the measurement test checks that the data class and
sensorEndTimesurvive. The protocol test checks that the summary measure keeps its configuration class. CAMS adds a task with its own measures, such as errors, to eachSmartphoneStudyProtocol. The test therefore finds the measures by type instead of takingtasks.first. - Set every field: a round trip with
nullin a field cannot fail on that field. The registration test ofcarp_polar_packageleaves a fieldnulland misses that the field cannot be read back. A study with a Polar sensor therefore fails to load after a restart.
Test the package declarations
Create test/package_test.dart, which checks what the package declares to CAMS:
- Measure types: each type must parse back to itself. CAMS splits a type at the first occurrence of its name, so
com.example.examplewould come back ascom.example. The second test checks that each data class has the type of its measure. - Default sampling schemes: the tests read the defaults from
SamplingPackageRegistry().samplingSchemes, the combined map that probes use, and check that the measures declare no permissions. - Probes: the registry creates the right probe for each type and gives it the device manager of the phone.
create()of the package returnsnullfor an unknown type. - Privacy: the tests apply the default privacy schema, and the first test applies it twice, because CAMS may transform a measurement more than once. See Make privacy functions safe to repeat.
Test the probes
Create test/probes_test.dart, which runs both probes against a mocked barometer. sensors_plus talks to the platform over two channels: a method channel that sets the sampling period, and an event channel that delivers the readings. The tests mock both:
- Mock both channels:
sensors_plussets the sampling period without waiting for the result. A test without the mock of the method channel therefore fails with an unhandledMissingPluginException. - Send readings in the plugin's format: each reading on the event channel is a
Float64Listof the pressure in hectopascals and the time in microseconds.sensors_plusturns it into aBarometerEvent. - Simulate a missing barometer:
events.error()with the codeNO_SENSORreproduces an Android phone without a barometer.mockMissingIosBarometer()makes the nativelistencall fail as on iOS, and the test replacesFlutterError.onErrorto count the reports. - Create probes without a study:
createProbe()creates a probe through the registry and initializes it with only a measure. The probe then takes its configuration from the measure, or else from the package default. - Wait for the state:
resume()returns the next state onstateEvents, which CAMS sets whenonResume()completes. - Shorten timeouts:
QuickAirPressureProbeoverridesfirstReadingTimeout, so a test of a silent barometer takes 300 milliseconds instead of 5 seconds. - Test the resume guard: the last test resumes the window probe twice and checks that the windows do not double.
Check the values, not only the types. A test that checks only the type of the data passes while the values are wrong. carp_movesense_package tests its motion data this way, and misses that it fills the gyroscope and magnetometer values with accelerometer values.
If your measures declare permissions, a probe does not start in a host test. The permission check needs the platform code of permission_handler, so the probe reports a MissingPluginException and returns false from onResume(). In a test subclass, override hasRequiredPermissions() to return true, as the probes_test.dart of CAMS does.
Run the tests
Generate the JSON code, then analyze and test the package from its folder:
The tests also print CAMS log lines. The warnings "Cannot read the barometer" and "No barometer reading yet" come from the tests that simulate a missing barometer.
Run the package in an app
Unit tests mock the barometer, so they do not show how the probes behave on a real phone. Before you publish, run the example app on an Android phone and an iPhone that have a barometer:
Check that both measures arrive on each platform. iOS needs the NSMotionUsageDescription entry, and sensors_plus ignores the requested sampling period there. For the setup of the example app and the output to expect, see Use the package in an app.
Publish the package
Publish the package on pub.dev when its tests pass, it runs on a phone and it avoids the common pitfalls.
Publishing is permanent
pub.dev does not let you unpublish a package. Within 7 days of publication, you can retract a version so that new apps do not adopt it, but it stays listed.
Choose the final names
Replace
com.examplewith a reverse domain name that you control, and give the package and its classes their final names. Every protocol and stored measurement holds the measure types, and the JSON type of a configuration class holds its class name. A later change breaks the studies and data that use the old names.Complete the pubspec
Remove
publish_to: none, which keeps the example private, and the comment line above it. Then add the URL of the repository:pubspec.yaml Keep the dependency constraints of the example.
carp_mobile_sensing: ^2.5.1accepts every 2.x version from 2.5.1 on.For a plugin that CAMS also uses, such as
sensors_plus, use the constraint of CAMS. An app gets only one version of each package. If your package requires another major version than CAMS does,flutter pub getfails in every app that uses both.Use semantic versioning. A change that stored protocols or data cannot handle, such as a new measure type string or a renamed field, needs a new major version.
Add a license
pub.dev requires a
LICENSEfile in the package folder. Replace the placeholder text thatflutter createwrites with a real license; the CARP packages use the MIT license. The dry run in a later step fails without aLICENSEfile but accepts the placeholder text, so check the file yourself.Write the README and the changelog
pub.dev shows the README on the page of the package. Describe what an app developer needs:
- the measure types, with their data and default configurations;
- the setup that an app needs on Android and iOS, such as
NSMotionUsageDescriptionfor the barometer; - the registration order:
CarpMobileSensing.ensureInitialized(), thenregister(), thenconfigure(); - the permissions that each measure asks for, and the privacy functions with the
privacySchemaNamethat turns them on; - the limits, such as the missing data on the iOS Simulator.
Add a section for each version to the changelog, newest first:
CHANGELOG.md Check the package
The dry run lists the files that it would upload, including the example app, and validates the package. pub.dev also scores the package with pub points, for example for its documentation and its static analysis. To see the score before you publish, run
panaon a copy of the package folder, as Scoring on pub.dev describes.Upload the package
dart pub publishasks you to sign in with a Google account, which pub.dev uses to manage who can upload new versions of the package.
Tell the CARP team
To have the package listed with the other sampling packages, tell the CARP team about it. Write on Discord or open an issue in carp.sensing-flutter. Report problems in CAMS itself the same way, such as the CAMS 2.5.1 defects in the common pitfalls.
Troubleshooting
Run the app on a phone. The iOS Simulator has no barometer, and sensors_plus reports this only in the Flutter log. After 5 seconds, both probes log that they have no reading yet and keep listening:
Call CarpMobileSensing.ensureInitialized() before the first use of SamplingPackageRegistry(), in the tests and in the app. See Startup order.
Mock the platform channel that the message names:
| Channel in the message | Cause and fix |
|---|---|
dev.fluttercommunity.plus/sensors/method | The method channel of sensors_plus is not mocked. Mock it as well as the event channel, as mockMethodChannel() in test/probes_test.dart does. |
flutter.baseflow.com/permissions/methods | The measure declares permissions. Override hasRequiredPermissions() in a test subclass of the probe. |
No fix is needed: the probes start anyway. In CAMS 2.5.1, the warning "Trying to resume a SmartphoneDeploymentExecutor" with "Ignoring this." appears when the app calls resume() right after configure() or tryDeployment(). For the reason, see Sampling package class.
Register the missing class in onRegister(), and add a JSON round trip test for it. The warning "Failed to load studies" means that CAMS failed to decode a stored deployment, usually because a class in it is not registered. CAMS then drops that study and every study stored after it.
For the classes to register, see Register the JSON classes and privacy functions.
Next step
Common pitfalls
The rules that keep a sampling package from losing data.