Mobly Test Framework

repository·master·Indexed 20 days ago

https://github.com/google/mobly

A Python-based automation framework version 1.13 specialized for end-to-end testing of complex environments involving multiple devices, custom hardware, or IoT setups. It provides controllers for Android devices, signal attenuators, iPerf servers, and Monsoon power monitoring, along with support for Android instrumentation tests and Mobly Snippets for device-side code execution.

Tokens
18.6K
Snippets
55
Records
75
Agent score
73%

What's inside Mobly

  1. Overview of the mobly.controllers.android_device_lib package

    master

    The mobly.controllers.android_device_lib package provides the necessary components for controlling Android devices within Mobly tests. It includes support for various communication protocols and tools such as ADB, Fastboot, SL4A, and Mobly Snippets.

    Key functional areas include:

    • ADB & Fastboot: Modules for interacting with devices via Android Debug Bridge and Fastboot.
    • Mobly Snippets: Support for the snippet_client and snippet_event to interact with Mobly Snippets running on the device.
    • Communication Protocols: Implementations for JSON-RPC (jsonrpc_client_base, jsonrpc_shell_base) and SL4A (sl4a_client).
    • Service Management: The service_manager handles the lifecycle and coordination of services on the device.
    • Event Handling: event_dispatcher and callback_handler for managing asynchronous events and callbacks.
  2. Explore Mobly controller modules

    master

    The mobly.controllers package provides specialized modules to control various hardware and software components within a test environment. Depending on your test requirements, you can use the following modules:

    • mobly.controllers.android_device: For controlling Android devices.
    • mobly.controllers.attenuator: For controlling signal attenuators.
    • mobly.controllers.iperf_server: For managing iPerf servers to measure network throughput.
    • mobly.controllers.monsoon: For interacting with Monsoon power monitoring hardware.
    • mobly.controllers.sniffer: For managing network sniffers.

    Additionally, there are specialized library subpackages available for more complex implementations:

    • mobly.controllers.android_device_lib
    • mobly.controllers.attenuator_lib
    • mobly.controllers.sniffer_lib
  3. Generate tests dynamically with `generate_tests`

    master

    Instead of duplicating test logic for different parameters, you can use Mobly's Generated tests feature. This allows you to run the same logic multiple times with different argument sets.

    1. Implement pre_run: In this method, define the list of arguments (arg_sets) and call self.generate_tests.
    2. Define test_logic: A function containing the actual test steps that accepts the arguments from arg_sets.
    3. Define name_func: A function that returns a unique string for each generated test case, using the same signature as test_logic.

    Example Implementation:

    from mobly import base_test
    from mobly import test_runner
    from mobly.controllers import android_device
    
    class ManyGreetingsTest(base_test.BaseTestClass):
    
        def pre_run(self):
            # List of (greeting, name) tuples
            messages = [('Hello', 'World'), ('Aloha', 'Obama'), ('konichiwa', 'Satoshi')]
            
            self.generate_tests(
                test_logic=self.make_toast_logic,
                name_func=self.make_toast_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)
    
        def make_toast_logic(self, greeting, name):
            self.dut.mbs.makeToast('%s, %s!' % (greeting, name))
    
        def make_toast_name_function(self, greeting, name):
            return 'test_greeting_say_%s_to_%s' % (greeting, name)
    
    if __name__ == '__main__':
      test_runner.main()
    from mobly import base_test
    from mobly import test_runner
    from mobly.controllers import android_device
    
    class ManyGreetingsTest(base_test.BaseTestClass):
    
        def pre_run(self):
            messages = [('Hello', 'World'), ('Aloha', 'Obama'), ('konichiwa', 'Satoshi')]
            
            self.generate_tests(
                test_logic=self.make_toast_logic,
                name_func=self.make_toast_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)
    
        def make_toast_logic(self, greeting, name):
            self.dut.mbs.makeToast('%s, %s!' % (greeting, name))
    
        def make_toast_name_function(self, greeting, name):
            return 'test_greeting_say_%s_to_%s' % (greeting, name)
    
    if __name__ == '__main__':
      test_runner.main()
  4. Control Android devices using Mobly Snippets

    master

    Mobly Snippet projects allow you to trigger custom code on Android devices from your host-side Mobly tests. This is useful for interacting with Android libraries like UI Automator and Espresso.

    • Mobly Snippet Lib: Used for triggering custom device-side code from host-side tests.
    • Mobly Bundled Snippets: Provides a simplified version of the public Android API specifically designed for Mobly testing control.
  5. Implement a custom Mobly controller module

    master

    To control custom hardware (like smart lights or switches), you must create a controller module that implements a specific interface. This module allows Mobly to manage the lifecycle of your custom devices.

    Required Functions

    Your module must provide these top-level functions:

    • create(configs): Receives a list of configuration dictionaries and returns a list of instantiated device objects.
    • destroy(objects): Receives the list of device objects and performs cleanup (e.g., closing connections, powering off devices).

    Optional Functions

    • get_info(objects): Receives the list of device objects and returns a list of dictionaries containing metadata. This information is included in the Mobly test report.
    def create(configs: List[Dict[str, Any]]) -> List[Any]:
        # Instantiate devices from configs
        return [Device(**cfg) for cfg in configs]
    
    def destroy(objects: List[Any]) -> None:
        # Cleanup devices
        for obj in objects:
            obj.close()
    
    def get_info(objects: List[Any]) -> List[Dict[str, Any]]:
        # Return metadata for reports
        return [{"id": obj.id} for obj in objects]
  6. Implement long-running services for AndroidDevice

    master

    Mobly's AndroidDevice controller allows you to run long-running services (like adb logcat collection or screen recording) that persist even when the device state changes (e.g., during a reboot).

    To implement a service, you must create a class that inherits from base_service.BaseService. You should override the following methods/properties:

    • start(): Logic to begin the service.
    • stop(): Logic to terminate the service.
    • is_alive (property): Returns the current status of the service.

    Additionally, you can implement optional pause() and resume() methods if your service needs to handle device disconnections that do not involve a full reboot.

    class MyService(base_service.BaseService):
      def __init__(self, device, configs=None):
        self._device = device
        self._configs = configs
        self._is_alive = False
    
      @property
      def is_alive(self):
        return self._is_alive
    
      def start(self):
        self._is_alive = True
    
      def stop(self):
        self._is_alive = False
  7. Install Mobly via pip or source

    master

    You can install Mobly using the released package from PyPI or by installing from the source for the latest features.

    Using pip (Recommended):

    pip install mobly

    Using source:

    git clone https://github.com/google/mobly.git
    cd mobly
    pip install -e .

    Note: You may need sudo if your system has permission restrictions.

    pip install mobly
  8. Run basic Android instrumentation tests

    master

    To run basic instrumentation tests, subclass base_instrumentation_test.BaseInstrumentationTestClass. In your test class, use self.register_controller(android_device) to get your device and self.run_instrumentation_test to execute the tests against a specific package name.

    Configuration (sample_config.yml):

    TestBeds:
      - Name: BasicTestBed
        Controllers:
            AndroidDevice: '*'

    Test Implementation (instrumentation_test.py):

    from mobly import base_instrumentation_test
    from mobly import test_runner
    from mobly.controllers import android_device
    
    class InstrumentationTest(base_instrumentation_test.BaseInstrumentationTestClass):
        def setup_class(self):
            self.dut = self.register_controller(android_device)[0]
    
        def test_instrumentation(self):
            self.run_instrumentation_test(self.dut, 'com.example.package.test')
    
    if __name__ == '__main__':
      test_runner.main()

    Execution:

    python instrumentation_test.py -c sample_config.yml
  9. Test with multiple Android devices

    master

    When testing interactions between multiple devices (e.g., Bluetooth discovery), follow these steps:

    1. Register multiple controllers: Use self.register_controller(android_device, min_number=N) where N is the required number of devices.
    2. Identify devices by label: Use android_device.get_device(self.ads, label='your_label') to retrieve specific devices from the controller list.
    3. Set debug tags: Assign device.debug_tag = 'tag_name' to differentiate device logs.
    4. Use asserts and logging: Use mobly.asserts for validation and device.log.info() for device-specific logging.

    Example Implementation:

    import logging
    from mobly import asserts
    from mobly import base_test
    from mobly import test_runner
    from mobly.controllers import android_device
    
    class BluetoothTest(base_test.BaseTestClass):
        def setup_class(self):
            self.ads = self.register_controller(android_device, min_number=2)
            self.discoverer = android_device.get_device(self.ads, label='discoverer')
            self.discoverer.debug_tag = 'discoverer'
            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):
            self.target.mbs.btEnable()
            self.discoverer.mbs.btEnable()
            self.target.mbs.btSetName('LookForMe!')
    
        def test_bluetooth_discovery(self):
            target_name = self.target.mbs.btGetName()
            self.target.mbs.btBecomeDiscoverable(60)
            discovered_devices = self.discoverer.mbs.btDiscoverAndGetResults()
            discovered_names = [device['Name'] for device in discovered_devices]
            asserts.assert_true(target_name in discovered_names, 'Discovery failed')
    
        def teardown_test(self):
            self.target.mbs.btDisable()
            self.discoverer.mbs.btDisable()
    
    if __name__ == '__main__':
      test_runner.main()
  10. Create a basic Mobly test with Android devices

    master

    To create a basic test, define a configuration file (.yml) to specify controllers and a Python test script inheriting from base_test.BaseTestClass.

    In the test script:

    1. Use self.register_controller(android_device) in setup_class to declare dependency on Android hardware.
    2. Access devices from the returned controller list (e.g., self.dut = self.ads[0]).
    3. Load snippets using self.dut.load_snippet('name', package_path) to enable device actions.

    Configuration (sample_config.yml): Using AndroidDevice: '*' tells the runner to find all connected Android devices automatically. You can also specify devices by serial number and add custom attributes.

    TestBeds:
      - Name: SampleTestBed
        Controllers:
            AndroidDevice: '*'

    Test Script (hello_world_test.py):

    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
        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()

    Execution:

    $ python hello_world_test.py -c sample_config.yml
    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!')
    
    if __name__ == '__main__':
      test_runner.main()
  11. Pass instrumentation options via configuration

    master

    You can pass instrumentation options (like test filters) through the Mobly configuration file using TestParams. In your test class, use self.parse_instrumentation_options(self.user_params) to retrieve these options, then pass them to self.run_instrumentation_test(..., options=self.options).

    Configuration (sample_config.yml):

    TestBeds:
      - Name: BasicTestBed
        Controllers:
            AndroidDevice: '*'
        TestParams:
            instrumentation_option_annotation: android.support.test.filters.LargeTest
            instrumentation_option_nonAnnotation: android.support.test.filters.SmallTest

    Test Implementation Snippet:

    class InstrumentationTest(base_instrumentation_test.BaseInstrumentationTestClass):
        def setup_class(self):
            self.dut = self.register_controller(android_device)[0]
            self.options = self.parse_instrumentation_options(self.user_params)
    
        def test_instrumentation(self):
            self.run_instrumentation_test(self.dut, 'com.example.package.test',
                options=self.options)
  12. Setup requirements for Android instrumentation tests

    master

    To run Android instrumentation tests with Mobly, ensure the following requirements are met:

    • A computer with at least 1 USB port.
    • Mobly package and its system dependencies installed.
    • An Android device compatible with your instrumentation and application APKs.
    • Your instrumentation and application APKs ready for installation.
    • A working adb setup. Verify this by connecting an Android device with "USB debugging" enabled and ensuring it appears in the output of adb devices.