AndroidX Core Team | 03b4da3 | 2021-03-10 23:20:41 +0000 | [diff] [blame] | 1 | # Testability |
| 2 | |
| 3 | [TOC] |
| 4 | |
| 5 | ## How to write testable libraries |
| 6 | |
| 7 | When developers use a Jetpack library, it should be easy to write reliable and |
| 8 | automated tests for their own code's functionality. In most cases, tests written |
| 9 | against a library will need to interact with that library in some way -- setting |
| 10 | up preconditions, reaching synchronization points, making calls -- and the |
| 11 | library should provide necessary functionality to enable such tests, either |
| 12 | through public APIs or optional `-testing` artifacts. |
| 13 | |
| 14 | **Testability**, in this document, means how easily and effectively the users of |
| 15 | a library can create tests for apps that use that library. |
| 16 | |
| 17 | NOTE Tests that check the behavior of a library have a different mission than |
| 18 | tests made by app developers using that library; however, library developers may |
| 19 | find themselves in a similar situation when writing tests for sample code. |
| 20 | |
| 21 | Often, the specifics of testability will vary from library to library and there |
| 22 | is no one way of providing it. Some libraries have enough public API surface, |
| 23 | others provide additional testing artifacts (e.g. |
| 24 | [Lifecycle Runtime Testing artifact](https://maven.google.com/web/index.html?q=lifecycle#androidx.lifecycle:lifecycle-runtime-testing)). |
| 25 | |
| 26 | The best way to check if your library is testable is to try to write a sample |
| 27 | app with tests. Unlike regular library tests, these apps will be limited to the |
| 28 | public API surface of the library. |
| 29 | |
| 30 | Keep in mind that a good test for a sample app should have: |
| 31 | |
| 32 | * [No side effects](#side-effects) |
| 33 | * [No dependencies on time / looper (except for UI)](#external-dependencies) |
| 34 | * [No private API calls](#private-apis) |
| 35 | * [No assumptions on undefined library behavior](#undefined-behavior) |
| 36 | |
| 37 | If you are able to write such tests for your library, you are good to go. If you |
| 38 | struggled or found yourself writing duplicate testing code, there is room for |
| 39 | improvement. |
| 40 | |
| 41 | To get started with sample code, see |
| 42 | [Sample code in Kotlin modules](api_guidelines.md#sample-code-in-kotlin-modules) |
| 43 | for information on writing samples that can be referenced from API reference |
| 44 | documentation or [Project directory structure](policies.md#directory-structure) |
| 45 | for module naming guidelines if you'd like to create a basic test app. |
| 46 | |
| 47 | ### Avoiding side-effects {#side-effects} |
| 48 | |
| 49 | #### Ensure proper scoping for your library a.k.a. Avoid Singletons |
| 50 | |
| 51 | Singletons are usually bad for tests as they live across different tests, |
| 52 | opening the gates for possible side-effects between tests. When possible, try to |
| 53 | avoid using singletons. If it is not possible, consider providing a test |
| 54 | artifact that will reset the state of the singleton between tests. |
| 55 | |
| 56 | #### Side effects due to external resources |
| 57 | |
| 58 | Sometimes, your library might be controlling resources on the device in which |
| 59 | case even if it is not a singleton, it might have side-effects. For instance, |
| 60 | Room, being a database library, inherently modifies a file on the disk. To allow |
| 61 | proper isolated testing, Room provides a builder option to create the database |
| 62 | [in memory](https://developer.android.com/reference/androidx/room/Room#inMemoryDatabaseBuilder\(android.content.Context,%20java.lang.Class%3CT%3E\)) |
| 63 | A possible alternative solution could be to provide a test rule that will |
| 64 | properly close databases after each test. |
| 65 | |
| 66 | If your library needs an inherently singleton resource (e.g. `WorkManager` is a |
| 67 | wrapper around `JobScheduler` and there is only 1 instance of it provided by the |
| 68 | system), consider providing a testing artifact. To provide isolation for tests, |
| 69 | the WorkManager library ships a |
| 70 | [separate testing artifact](https://developer.android.com/topic/libraries/architecture/workmanager/how-to/integration-testing) |
| 71 | |
| 72 | ### "External" dependencies {#external-dependencies} |
| 73 | |
| 74 | #### Allow configuration of external resource dependencies |
| 75 | |
| 76 | A common example of this use case is libraries that do multi-threaded |
| 77 | operations. For Kotlin libraries, this is usually achieved by receiving a |
| 78 | coroutine context or scope. For Java libraries, it is commonly an `Executor`. If |
| 79 | you have a case like this, make sure it can be passed as a parameter to your |
| 80 | library. |
| 81 | |
| 82 | NOTE Android API Guidelines require that methods accepting a callback |
| 83 | [must also take an Executor](https://android.googlesource.com/platform/developers/docs/+/refs/heads/master/api-guidelines/index.md#callbacks-listener) |
| 84 | |
| 85 | For example, the Room library allows developers to |
| 86 | [pass different executors](https://developer.android.com/reference/androidx/room/RoomDatabase.Builder#setQueryExecutor\(java.util.concurrent.Executor\)) |
| 87 | for background query operations. When writing a test, developers can invoke this |
| 88 | with a custom executor where they can track work completion. |
| 89 | |
| 90 | * [sample test](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-master-dev:room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/SuspendingQueryTest.kt;l=672) |
| 91 | |
| 92 | If the external resource you require does not make sense as a public API, such |
| 93 | as a main thread executor, then you can provide a testing artifact which will |
| 94 | allow setting it. For example, the Lifecycle package depends on the main thread |
| 95 | executor to function but for an application, customizing it does not make sense |
| 96 | (as there is only 1 "pre-defined" main thread for an app). For testing purposes, |
| 97 | the Lifecycle library provides a testing artifact which includes |
| 98 | [TestRules](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-master-dev:arch/core-testing/src/main/java/androidx/arch/core/executor/testing/CountingTaskExecutorRule.java) |
| 99 | to change them. |
| 100 | |
| 101 | #### Fakes for external dependencies |
| 102 | |
| 103 | Sometimes, the developer might want to track side effects of your library for |
| 104 | end to end testing. For instance, if your library provides some functionality |
| 105 | that might decide to toggle bluetooth, outside developer's direct control, it |
| 106 | might be a good idea to have an interface for that functionality and also |
| 107 | provide a fake that can record such calls. If you don't think that interface |
| 108 | makes sense as a library configuration, you can |
| 109 | [restrict](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-master-dev:annotation/annotation/src/main/java/androidx/annotation/RestrictTo.java) |
| 110 | that interface to your library group and provide a testing artifacts with the |
| 111 | fake so that developer can observe side effects only in tests while you can |
| 112 | avoid creating unnecessary APIs. |
| 113 | |
| 114 | NOTE There is a fine balance between making a library configurable and making |
| 115 | configuration a nightmare for the developer. You should try to always have |
| 116 | defaults for these configurable objects to ensure your library is easy to use |
| 117 | while still testable when necessary. |
| 118 | |
| 119 | ### Avoiding the need for private API calls in tests {#private-apis} |
| 120 | |
| 121 | #### Provide additional functionality for tests |
| 122 | |
| 123 | There are certain situations where it could be useful to provide more APIs that |
| 124 | only make sense in the scope of testing. For example, a `Lifecycle` class has |
| 125 | methods that are bound to the main thread but developers may want to have other |
| 126 | tests that rely on lifecycle but do not run on the main thread (or even on an |
| 127 | emulator). For such cases, you may create APIs that are testing only to allow |
| 128 | developers to use them only in tests while giving them the confidence that it |
| 129 | will behave as close as possible to a real implementation. For the case above, |
| 130 | `LifecycleRegistry` provides an API to |
| 131 | [create](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-master-dev:lifecycle/lifecycle-runtime/src/main/java/androidx/lifecycle/LifecycleRegistry.java;l=334) |
| 132 | an instance of it that will not enforce thread restrictions. |
| 133 | |
| 134 | NOTE Even though the implementation referenced above is acceptable, it is always |
| 135 | better to create such functionality in additional testing artifacts when |
| 136 | possible. |
| 137 | |
| 138 | ### Avoiding assumptions in app code for library behavior {#undefined-behavior} |
| 139 | |
| 140 | #### Provide fakes for common classes in a `-testing` artifact |
| 141 | |
| 142 | In some cases, the developer might need an instance of a class provided by your |
| 143 | library but does not want to (or cannot) initiate it in tests. Moreover, |
| 144 | behavior of such classes might not be fully defined for edge cases, making it |
| 145 | difficult for developers to mock them. |
| 146 | |
| 147 | For such cases, it is a good practice to provide a fake implementation out of |
| 148 | the box that can be controlled in tests. For example, the Lifecycle library |
| 149 | provides a |
| 150 | [fake implementation](https://cs.android.com/androidx/platform/frameworks/support/+/androidx-master-dev:lifecycle/lifecycle-runtime-testing/src/main/java/androidx/lifecycle/testing/TestLifecycleOwner.kt) |
| 151 | for the `LifecycleOwner` class that can be manipulated in tests to create |
| 152 | different use cases. |
| 153 | |
| 154 | ## Document how to test with your library |
| 155 | |
| 156 | Even when your library is fully testable, it is often not clear for a developer |
| 157 | to know which APIs are safe to call in tests and when. Providing guidance on |
| 158 | [d.android.com](https://d.android.com) will make it much easier for the |
| 159 | developer to start testing with your library. |
| 160 | |
| 161 | ## Testability anti-patterns |
| 162 | |
| 163 | When writing tests against your APIs, look out for the following anti-patterns. |
| 164 | |
| 165 | ### Calling `Instrumentation.waitForIdleSync()` as a synchronization barrier |
| 166 | |
| 167 | The `waitForIdle()` and `waitForIdleSync()` methods claim to "(Synchronously) |
| 168 | wait for the application to be idle." and may seem like reasonable options when |
| 169 | there is no obvious way to observe whether an action has completed; however, |
| 170 | these methods know nothing about the context of the test and return when the |
| 171 | main thread's message queue is empty. |
| 172 | |
| 173 | ```java {.bad} |
| 174 | view.requestKeyboardFocus(); |
| 175 | Instrumentation.waitForIdleSync(); |
| 176 | sendKeyEvents(view, "1234"); |
| 177 | // There is no guarantee that `view` has keyboard focus yet. |
| 178 | device.pressEnter(); |
| 179 | ``` |
| 180 | |
| 181 | In apps with an active UI animation, the message queue is _never empty_. If the |
| 182 | app is waiting for a callback across IPC, the message queue may be empty despite |
| 183 | the test not reaching the desired state. |
| 184 | |
| 185 | In some cases, `waitForIdleSync()` introduces enough of a delay that unrelated |
| 186 | asynchronous actions happen to have completed by the time the method returns; |
| 187 | however, this delay is purely coincidental and eventually leads to flakiness. |
| 188 | |
| 189 | Instead, find a reliable synchronization barrier that guarantees the expected |
| 190 | state has been reached or the requested action has been completed. This might |
| 191 | mean adding listeners, callbacks, `ListenableFuture`s, or `LiveData`. |
| 192 | |
| 193 | See [Asynchronous work](api_guidelines.md#async) in the API Guidelines for more |
| 194 | information on exposing the state of asynchronous work to clients. |