| # Getting started with Mobly |
| |
| This tutorial shows how to write and execute simple Mobly test cases. We are |
| using Android devices here since they are pretty accessible. Mobly supports |
| various devices and you can also use your own custom hardware/equipment. |
| |
| ## Setup Requirements |
| |
| * A computer with at least 2 USB ports. |
| * Mobly package and its system dependencies installed on the computer. |
| * One or two Android devices with the [Mobly Bundled Snippets]( |
| https://github.com/google/mobly-bundled-snippets) (MBS) installed. We will |
| use MBS to trigger actions on the Android devices. |
| * A working adb setup. To check, connect one Android device to the computer |
| and make sure it has "USB debugging" enabled. Make sure the device shows up |
| in the list printed by `adb devices`. |
| |
| ## Example 1: Hello World! |
| |
| Let's start with the simple example of posting "Hello World" on the Android |
| device's screen. Create the following files: |
| |
| **sample_config.yml** |
| |
| ```yaml |
| TestBeds: |
| # A test bed where adb will find Android devices. |
| - Name: SampleTestBed |
| Controllers: |
| AndroidDevice: '*' |
| ``` |
| |
| **hello_world_test.py** |
| |
| ```python |
| from mobly import base_test |
| from mobly import test_runner |
| from mobly.controllers import android_device |
| |
| class HelloWorldTest(base_test.BaseTestClass): |
| |
| def setup_class(self): |
| # Registering android_device controller module declares the test's |
| # dependency on Android device hardware. By default, we expect at least one |
| # object is created from this. |
| self.ads = self.register_controller(android_device) |
| self.dut = self.ads[0] |
| # Start Mobly Bundled Snippets (MBS). |
| self.dut.load_snippet('mbs', android_device.MBS_PACKAGE) |
| |
| def test_hello(self): |
| self.dut.mbs.makeToast('Hello World!') |
| |
| if __name__ == '__main__': |
| test_runner.main() |
| ``` |
| |
| To execute: |
| |
| ``` |
| $ python hello_world_test.py -c sample_config.yml |
| ``` |
| |
| *Expect*: |
| |
| A "Hello World!" toast notification appears on your device's screen. |
| |
| Within SampleTestBed's `Controllers` section, we used `AndroidDevice: '*'` to tell |
| the test runner to automatically find all connected Android devices. You can also |
| specify particular devices by serial number and attach extra attributes to the object: |
| |
| ```yaml |
| AndroidDevice: |
| - serial: xyz |
| phone_number: 123456 |
| - serial: abc |
| label: golden_device |
| ``` |
| |
| ## Example 2: Invoking specific test case |
| |
| We have multiple tests written in a test script, and we only want to execute |
| a subset of them. |
| |
| **hello_world_test.py** |
| |
| ```python |
| from mobly import base_test |
| from mobly import test_runner |
| from mobly.controllers import android_device |
| |
| class HelloWorldTest(base_test.BaseTestClass): |
| |
| def setup_class(self): |
| self.ads = self.register_controller(android_device) |
| self.dut = self.ads[0] |
| self.dut.load_snippet('mbs', android_device.MBS_PACKAGE) |
| |
| def test_hello(self): |
| self.dut.mbs.makeToast('Hello World!') |
| |
| def test_bye(self): |
| self.dut.mbs.makeToast('Goodbye!') |
| |
| if __name__ == '__main__': |
| test_runner.main() |
| ``` |
| |
| *To execute:* |
| |
| ``` |
| $ python hello_world_test.py -c sample_config.yml --test_case test_bye |
| ``` |
| |
| *Expect*: |
| |
| A "Goodbye!" toast notification appears on your device's screen. |
| |
| You can dictate what test cases to execute within a test script and their |
| execution order, for example: |
| |
| ``` |
| $ python hello_world_test.py -c sample_config.yml --test_case test_bye test_hello test_bye |
| ``` |
| |
| *Expect*: |
| |
| Toast notifications appear on your device's screen in the following order: |
| "Goodbye!", "Hello World!", "Goodbye!". |
| |
| ## Example 3: User parameters |
| |
| You could specify user parameters to be passed into your test class in the |
| config file. |
| |
| In the following config, we added a parameter `favorite_food` to be used in the test case. |
| |
| **sample_config.yml** |
| |
| ```yaml |
| TestBeds: |
| - Name: SampleTestBed |
| Controllers: |
| AndroidDevice: '*' |
| TestParams: |
| favorite_food: Green eggs and ham. |
| ``` |
| |
| In the test script, you could access the user parameter: |
| |
| ```python |
| def test_favorite_food(self): |
| food = self.user_params.get('favorite_food') |
| if food: |
| self.dut.mbs.makeToast("I'd like to eat %s." % food) |
| else: |
| self.dut.mbs.makeToast("I'm not hungry.") |
| ``` |
| |
| ## Example 4: Multiple Test Beds and Default Test Parameters |
| |
| Multiple test beds can be configured in one configuration file. |
| |
| **sample_config.yaml** |
| |
| ```yaml |
| # DefaultParams is optional here. It uses yaml's anchor feature to easily share |
| # a set of parameters between multiple test bed configs |
| DefaultParams: &DefaultParams |
| favorite_food: green eggs and ham. |
| |
| TestBeds: |
| - Name: XyzTestBed |
| Controllers: |
| AndroidDevice: |
| - serial: xyz |
| phone_number: 123456 |
| TestParams: |
| <<: *DefaultParams |
| - Name: AbcTestBed |
| Controllers: |
| AndroidDevice: |
| - serial: abc |
| label: golden_device |
| TestParams: |
| <<: *DefaultParams |
| ``` |
| |
| You can choose which one to execute on with the command line argument |
| `--test_bed`: |
| |
| ``` |
| $ python hello_world_test.py -c sample_config.yml --test_bed AbcTestBed |
| ``` |
| |
| *Expect*: |
| |
| A "Hello World!" and a "Goodbye!" toast notification appear on your device's |
| screen. |
| |
| |
| ## Example 5: Test with Multiple Android devices |
| |
| In this example, we use one Android device to discover another Android device |
| via bluetooth. This test demonstrates several essential elements in test |
| writing, like asserts, device debug tag, and general logging vs logging with device tag. |
| |
| **sample_config.yml** |
| |
| ```yaml |
| TestBeds: |
| - Name: TwoDeviceTestBed |
| Controllers: |
| AndroidDevice: |
| - serial: xyz |
| label: target |
| - serial: abc |
| label: discoverer |
| TestParams: |
| bluetooth_name: MagicBluetooth |
| bluetooth_timeout: 5 |
| |
| ``` |
| |
| **sample_test.py** |
| |
| |
| ```python |
| import logging |
| import pprint |
| |
| from mobly import asserts |
| from mobly import base_test |
| from mobly import test_runner |
| from mobly.controllers import android_device |
| |
| # Number of seconds for the target to stay discoverable on Bluetooth. |
| DISCOVERABLE_TIME = 60 |
| |
| |
| class HelloWorldTest(base_test.BaseTestClass): |
| def setup_class(self): |
| # Registering android_device controller module, and declaring that the test |
| # requires at least two Android devices. |
| self.ads = self.register_controller(android_device, min_number=2) |
| # The device used to discover Bluetooth devices. |
| self.discoverer = android_device.get_device( |
| self.ads, label='discoverer') |
| # Sets the tag that represents this device in logs. |
| self.discoverer.debug_tag = 'discoverer' |
| # The device that is expected to be discovered |
| self.target = android_device.get_device(self.ads, label='target') |
| self.target.debug_tag = 'target' |
| self.target.load_snippet('mbs', android_device.MBS_PACKAGE) |
| self.discoverer.load_snippet('mbs', android_device.MBS_PACKAGE) |
| |
| def setup_test(self): |
| # Make sure bluetooth is on. |
| self.target.mbs.btEnable() |
| self.discoverer.mbs.btEnable() |
| # Set Bluetooth name on target device. |
| self.target.mbs.btSetName('LookForMe!') |
| |
| def test_bluetooth_discovery(self): |
| target_name = self.target.mbs.btGetName() |
| self.target.log.info('Become discoverable with name "%s" for %ds.', |
| target_name, DISCOVERABLE_TIME) |
| self.target.mbs.btBecomeDiscoverable(DISCOVERABLE_TIME) |
| self.discoverer.log.info('Looking for Bluetooth devices.') |
| discovered_devices = self.discoverer.mbs.btDiscoverAndGetResults() |
| self.discoverer.log.debug('Found Bluetooth devices: %s', |
| pprint.pformat(discovered_devices, indent=2)) |
| discovered_names = [device['Name'] for device in discovered_devices] |
| logging.info('Verifying the target is discovered by the discoverer.') |
| asserts.assert_true( |
| target_name in discovered_names, |
| 'Failed to discover the target device %s over Bluetooth.' % |
| target_name) |
| |
| def teardown_test(self): |
| # Turn Bluetooth off on both devices after test finishes. |
| self.target.mbs.btDisable() |
| self.discoverer.mbs.btDisable() |
| |
| |
| if __name__ == '__main__': |
| test_runner.main() |
| |
| ``` |
| |
| There's potentially a lot more we could do in this test, e.g. check |
| the hardware address, see whether we can pair devices, transfer files, etc. |
| |
| To learn more about the features included in MBS, go to [MBS repo]( |
| https://github.com/google/mobly-bundled-snippets) to see how to check its help |
| menu. |
| |
| To learn more about Mobly Snippet Lib, including features like Espresso support |
| and asynchronous calls, see the [snippet lib examples]( |
| https://github.com/google/mobly-snippet-lib/tree/master/examples). |
| |
| |
| ## Example 6: Generated Tests |
| |
| A common use case in writing tests is to execute the same test logic multiple |
| times, each time with a different set of parameters. Instead of duplicating the |
| same test case with minor tweaks, you could use the **Generated tests** in |
| Mobly. |
| |
| Mobly could generate test cases for you based on a list of parameters and a |
| function that contains the test logic. Each generated test case is equivalent |
| to an actual test case written in the class in terms of execution, procedure |
| functions (setup/teardown/on_fail), and result collection. You could also |
| select generated test cases via the `--test_case` cli arg as well. |
| |
| |
| Here's an example of generated tests in action. We will reuse the "Example 1: |
| Hello World!". Instead of making one toast of "Hello World", we will generate |
| several test cases and toast a different message in each one of them. |
| |
| You could reuse the config file from Example 1. |
| |
| The test class would look like: |
| |
| |
| **many_greetings_test.py** |
| |
| ```python |
| from mobly import base_test |
| from mobly import test_runner |
| from mobly.controllers import android_device |
| |
| |
| class ManyGreetingsTest(base_test.BaseTestClass): |
| |
| # When a test run starts, Mobly calls this function to figure out what |
| # tests need to be generated. So you need to specify what tests to generate |
| # in this function. |
| def setup_generated_tests(self): |
| messages = [('Hello', 'World'), ('Aloha', 'Obama'), |
| ('konichiwa', 'Satoshi')] |
| # Call `generate_tests` function to specify the tests to generate. This |
| # function can only be called within `setup_generated_tests`. You could |
| # call this function multiple times to generate multiple groups of |
| # tests. |
| self.generate_tests( |
| # Specify the function that has the common logic shared by these |
| # generated tests. |
| test_logic=self.make_toast_logic, |
| # Specify a function that creates the name of each test. |
| name_func=self.make_toast_name_function, |
| # A list of tuples, where each tuple is a set of arguments to be |
| # passed to the test logic and name function. |
| arg_sets=messages) |
| |
| def setup_class(self): |
| self.ads = self.register_controller(android_device) |
| self.dut = self.ads[0] |
| self.dut.load_snippet('mbs', android_device.MBS_PACKAGE) |
| |
| # The common logic shared by a group of generated tests. |
| def make_toast_logic(self, greeting, name): |
| self.dut.mbs.makeToast('%s, %s!' % (greeting, name)) |
| |
| # The function that generates the names of each test case based on each |
| # argument set. The name function should have the same signature as the |
| # actual test logic function. |
| def make_toast_name_function(self, greeting, name): |
| return 'test_greeting_say_%s_to_%s' % (greeting, name) |
| |
| |
| if __name__ == '__main__': |
| test_runner.main() |
| ``` |
| |
| Three test cases will be executed even though we did not "physically" define |
| any "test_xx" function in the test class. |