1# Getting started with Mobly 2 3This tutorial shows how to write and execute simple Mobly test cases. We are 4using Android devices here since they are pretty accessible. Mobly supports 5various devices and you can also use your own custom hardware/equipment. 6 7## Setup Requirements 8 9* A computer with at least 2 USB ports. 10* Mobly package and its system dependencies installed on the computer. 11* One or two Android devices with the [Mobly Bundled Snippets]( 12 https://github.com/google/mobly-bundled-snippets) (MBS) installed. We will 13 use MBS to trigger actions on the Android devices. 14* A working adb setup. To check, connect one Android device to the computer 15 and make sure it has "USB debugging" enabled. Make sure the device shows up 16 in the list printed by `adb devices`. 17 18## Example 1: Hello World! 19 20Let's start with the simple example of posting "Hello World" on the Android 21device's screen. Create the following files: 22 23**sample_config.yml** 24 25```yaml 26TestBeds: 27 # A test bed where adb will find Android devices. 28 - Name: SampleTestBed 29 Controllers: 30 AndroidDevice: '*' 31``` 32 33**hello_world_test.py** 34 35```python 36from mobly import base_test 37from mobly import test_runner 38from mobly.controllers import android_device 39 40class HelloWorldTest(base_test.BaseTestClass): 41 42 def setup_class(self): 43 # Registering android_device controller module declares the test's 44 # dependency on Android device hardware. By default, we expect at least one 45 # object is created from this. 46 self.ads = self.register_controller(android_device) 47 self.dut = self.ads[0] 48 # Start Mobly Bundled Snippets (MBS). 49 self.dut.load_snippet('mbs', android_device.MBS_PACKAGE) 50 51 def test_hello(self): 52 self.dut.mbs.makeToast('Hello World!') 53 54if __name__ == '__main__': 55 test_runner.main() 56``` 57 58To execute: 59 60``` 61$ python hello_world_test.py -c sample_config.yml 62``` 63 64*Expect*: 65 66A "Hello World!" toast notification appears on your device's screen. 67 68Within SampleTestBed's `Controllers` section, we used `AndroidDevice: '*'` to tell 69the test runner to automatically find all connected Android devices. You can also 70specify particular devices by serial number and attach extra attributes to the object: 71 72```yaml 73AndroidDevice: 74 - serial: xyz 75 phone_number: 123456 76 - serial: abc 77 label: golden_device 78``` 79 80## Example 2: Invoking specific test case 81 82We have multiple tests written in a test script, and we only want to execute 83a subset of them. 84 85**hello_world_test.py** 86 87```python 88from mobly import base_test 89from mobly import test_runner 90from mobly.controllers import android_device 91 92class HelloWorldTest(base_test.BaseTestClass): 93 94 def setup_class(self): 95 self.ads = self.register_controller(android_device) 96 self.dut = self.ads[0] 97 self.dut.load_snippet('mbs', android_device.MBS_PACKAGE) 98 99 def test_hello(self): 100 self.dut.mbs.makeToast('Hello World!') 101 102 def test_bye(self): 103 self.dut.mbs.makeToast('Goodbye!') 104 105if __name__ == '__main__': 106 test_runner.main() 107``` 108 109*To execute:* 110 111``` 112$ python hello_world_test.py -c sample_config.yml --test_case test_bye 113``` 114 115*Expect*: 116 117A "Goodbye!" toast notification appears on your device's screen. 118 119You can dictate what test cases to execute within a test script and their 120execution order, for example: 121 122``` 123$ python hello_world_test.py -c sample_config.yml --test_case test_bye test_hello test_bye 124``` 125 126*Expect*: 127 128Toast notifications appear on your device's screen in the following order: 129"Goodbye!", "Hello World!", "Goodbye!". 130 131## Example 3: User parameters 132 133You could specify user parameters to be passed into your test class in the 134config file. 135 136In the following config, we added a parameter `favorite_food` to be used in the test case. 137 138**sample_config.yml** 139 140```yaml 141TestBeds: 142 - Name: SampleTestBed 143 Controllers: 144 AndroidDevice: '*' 145 TestParams: 146 favorite_food: Green eggs and ham. 147``` 148 149In the test script, you could access the user parameter: 150 151```python 152 def test_favorite_food(self): 153 food = self.user_params.get('favorite_food') 154 if food: 155 self.dut.mbs.makeToast("I'd like to eat %s." % food) 156 else: 157 self.dut.mbs.makeToast("I'm not hungry.") 158``` 159 160## Example 4: Multiple Test Beds and Default Test Parameters 161 162Multiple test beds can be configured in one configuration file. 163 164**sample_config.yaml** 165 166```yaml 167# DefaultParams is optional here. It uses yaml's anchor feature to easily share 168# a set of parameters between multiple test bed configs 169DefaultParams: &DefaultParams 170 favorite_food: green eggs and ham. 171 172TestBeds: 173 - Name: XyzTestBed 174 Controllers: 175 AndroidDevice: 176 - serial: xyz 177 phone_number: 123456 178 TestParams: 179 <<: *DefaultParams 180 - Name: AbcTestBed 181 Controllers: 182 AndroidDevice: 183 - serial: abc 184 label: golden_device 185 TestParams: 186 <<: *DefaultParams 187``` 188 189You can choose which one to execute on with the command line argument 190`--test_bed`: 191 192``` 193$ python hello_world_test.py -c sample_config.yml --test_bed AbcTestBed 194``` 195 196*Expect*: 197 198A "Hello World!" and a "Goodbye!" toast notification appear on your device's 199screen. 200 201 202## Example 5: Test with Multiple Android devices 203 204In this example, we use one Android device to discover another Android device 205via bluetooth. This test demonstrates several essential elements in test 206writing, like asserts, device debug tag, and general logging vs logging with device tag. 207 208**sample_config.yml** 209 210```yaml 211TestBeds: 212 - Name: TwoDeviceTestBed 213 Controllers: 214 AndroidDevice: 215 - serial: xyz 216 label: target 217 - serial: abc 218 label: discoverer 219 TestParams: 220 bluetooth_name: MagicBluetooth 221 bluetooth_timeout: 5 222 223``` 224 225**sample_test.py** 226 227 228```python 229import logging 230import pprint 231 232from mobly import asserts 233from mobly import base_test 234from mobly import test_runner 235from mobly.controllers import android_device 236 237# Number of seconds for the target to stay discoverable on Bluetooth. 238DISCOVERABLE_TIME = 60 239 240 241class HelloWorldTest(base_test.BaseTestClass): 242 def setup_class(self): 243 # Registering android_device controller module, and declaring that the test 244 # requires at least two Android devices. 245 self.ads = self.register_controller(android_device, min_number=2) 246 # The device used to discover Bluetooth devices. 247 self.discoverer = android_device.get_device( 248 self.ads, label='discoverer') 249 # Sets the tag that represents this device in logs. 250 self.discoverer.debug_tag = 'discoverer' 251 # The device that is expected to be discovered 252 self.target = android_device.get_device(self.ads, label='target') 253 self.target.debug_tag = 'target' 254 self.target.load_snippet('mbs', android_device.MBS_PACKAGE) 255 self.discoverer.load_snippet('mbs', android_device.MBS_PACKAGE) 256 257 def setup_test(self): 258 # Make sure bluetooth is on. 259 self.target.mbs.btEnable() 260 self.discoverer.mbs.btEnable() 261 # Set Bluetooth name on target device. 262 self.target.mbs.btSetName('LookForMe!') 263 264 def test_bluetooth_discovery(self): 265 target_name = self.target.mbs.btGetName() 266 self.target.log.info('Become discoverable with name "%s" for %ds.', 267 target_name, DISCOVERABLE_TIME) 268 self.target.mbs.btBecomeDiscoverable(DISCOVERABLE_TIME) 269 self.discoverer.log.info('Looking for Bluetooth devices.') 270 discovered_devices = self.discoverer.mbs.btDiscoverAndGetResults() 271 self.discoverer.log.debug('Found Bluetooth devices: %s', 272 pprint.pformat(discovered_devices, indent=2)) 273 discovered_names = [device['Name'] for device in discovered_devices] 274 logging.info('Verifying the target is discovered by the discoverer.') 275 asserts.assert_true( 276 target_name in discovered_names, 277 'Failed to discover the target device %s over Bluetooth.' % 278 target_name) 279 280 def teardown_test(self): 281 # Turn Bluetooth off on both devices after test finishes. 282 self.target.mbs.btDisable() 283 self.discoverer.mbs.btDisable() 284 285 286if __name__ == '__main__': 287 test_runner.main() 288 289``` 290 291There's potentially a lot more we could do in this test, e.g. check 292the hardware address, see whether we can pair devices, transfer files, etc. 293 294To learn more about the features included in MBS, go to [MBS repo]( 295https://github.com/google/mobly-bundled-snippets) to see how to check its help 296menu. 297 298To learn more about Mobly Snippet Lib, including features like Espresso support 299and asynchronous calls, see the [snippet lib examples]( 300https://github.com/google/mobly-snippet-lib/tree/master/examples). 301 302 303## Example 6: Generated Tests 304 305A common use case in writing tests is to execute the same test logic multiple 306times, each time with a different set of parameters. Instead of duplicating the 307same test case with minor tweaks, you could use the **Generated tests** in 308Mobly. 309 310Mobly could generate test cases for you based on a list of parameters and a 311function that contains the test logic. Each generated test case is equivalent 312to an actual test case written in the class in terms of execution, procedure 313functions (setup/teardown/on_fail), and result collection. You could also 314select generated test cases via the `--test_case` cli arg as well. 315 316 317Here's an example of generated tests in action. We will reuse the "Example 1: 318Hello World!". Instead of making one toast of "Hello World", we will generate 319several test cases and toast a different message in each one of them. 320 321You could reuse the config file from Example 1. 322 323The test class would look like: 324 325 326**many_greetings_test.py** 327 328```python 329from mobly import base_test 330from mobly import test_runner 331from mobly.controllers import android_device 332 333 334class ManyGreetingsTest(base_test.BaseTestClass): 335 336 # When a test run starts, Mobly calls this function to figure out what 337 # tests need to be generated. So you need to specify what tests to generate 338 # in this function. 339 def setup_generated_tests(self): 340 messages = [('Hello', 'World'), ('Aloha', 'Obama'), 341 ('konichiwa', 'Satoshi')] 342 # Call `generate_tests` function to specify the tests to generate. This 343 # function can only be called within `setup_generated_tests`. You could 344 # call this function multiple times to generate multiple groups of 345 # tests. 346 self.generate_tests( 347 # Specify the function that has the common logic shared by these 348 # generated tests. 349 test_logic=self.make_toast_logic, 350 # Specify a function that creates the name of each test. 351 name_func=self.make_toast_name_function, 352 # A list of tuples, where each tuple is a set of arguments to be 353 # passed to the test logic and name function. 354 arg_sets=messages) 355 356 def setup_class(self): 357 self.ads = self.register_controller(android_device) 358 self.dut = self.ads[0] 359 self.dut.load_snippet('mbs', android_device.MBS_PACKAGE) 360 361 # The common logic shared by a group of generated tests. 362 def make_toast_logic(self, greeting, name): 363 self.dut.mbs.makeToast('%s, %s!' % (greeting, name)) 364 365 # The function that generates the names of each test case based on each 366 # argument set. The name function should have the same signature as the 367 # actual test logic function. 368 def make_toast_name_function(self, greeting, name): 369 return 'test_greeting_say_%s_to_%s' % (greeting, name) 370 371 372if __name__ == '__main__': 373 test_runner.main() 374``` 375 376Three test cases will be executed even though we did not "physically" define 377any "test_xx" function in the test class. 378