Skip to content

Latest commit

 

History

History
 
 

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

README.md

MicroPythonOS Testing Guide

This directory contains the test suite for MicroPythonOS. Tests can run on both desktop (for fast iteration) and on-device (for hardware verification).

Quick Start

# Run all tests
./tests/unittest.sh

# Run a specific test
./tests/unittest.sh tests/test_graphical_keyboard_q_button_bug.py

# Run on device
./tests/unittest.sh tests/test_graphical_keyboard_q_button_bug.py --ondevice

Test Architecture

Directory Structure

tests/
├── base/                    # Base test classes (DRY patterns)
│   ├── __init__.py         # Exports GraphicalTestBase, KeyboardTestBase
│   ├── graphical_test_base.py
│   └── keyboard_test_base.py
├── screenshots/             # Captured screenshots for visual regression
├── test_*.py               # Test files
├── unittest.sh             # Test runner script
└── README.md               # This file

Testing Modules

MicroPythonOS provides two testing modules:

  1. mpos.testing - Hardware and system mocks

    • Location: internal_filesystem/lib/mpos/testing/
    • Use for: Mocking hardware (Pin, PWM, I2S, NeoPixel), network, async operations
  2. mpos.ui.testing - LVGL/UI testing utilities

    • Location: internal_filesystem/lib/mpos/ui/testing.py
    • Use for: UI interaction, screenshots, widget inspection

Base Test Classes

GraphicalTestBase

Base class for all graphical (LVGL) tests. Provides:

  • Automatic screen creation/cleanup
  • Screenshot capture
  • Widget finding utilities
  • Custom assertions
from base import GraphicalTestBase

class TestMyUI(GraphicalTestBase):
    def test_something(self):
        # self.screen is already created
        label = lv.label(self.screen)
        label.set_text("Hello")
        
        self.wait_for_render()
        self.assertTextPresent("Hello")
        self.capture_screenshot("my_test.raw")

Key Methods:

  • wait_for_render(iterations=5) - Process LVGL tasks
  • capture_screenshot(filename) - Save screenshot
  • find_label_with_text(text) - Find label widget
  • click_button(button) - Simulate button click
  • assertTextPresent(text) - Assert text is on screen
  • assertWidgetVisible(widget) - Assert widget is visible

KeyboardTestBase

Extends GraphicalTestBase for keyboard tests. Provides:

  • Keyboard and textarea creation
  • Reliable keyboard button clicking
  • Textarea assertions
from base import KeyboardTestBase

class TestMyKeyboard(KeyboardTestBase):
    def test_typing(self):
        self.create_keyboard_scene()
        
        self.click_keyboard_button("h")
        self.click_keyboard_button("i")
        
        self.assertTextareaText("hi")

Key Methods:

  • create_keyboard_scene() - Create textarea + MposKeyboard
  • click_keyboard_button(text) - Click keyboard button reliably
  • type_text(text) - Type a string
  • get_textarea_text() - Get textarea content
  • clear_textarea() - Clear textarea
  • assertTextareaText(expected) - Assert textarea content
  • assertTextareaEmpty() - Assert textarea is empty

Mock Classes

Import mocks from mpos.testing:

from mpos.testing import (
    # Hardware mocks
    MockMachine,      # Full machine module mock
    MockPin,          # GPIO pins
    MockPWM,          # PWM for buzzer
    MockI2S,          # Audio I2S
    MockTimer,        # Hardware timers
    MockNeoPixel,     # LED strips
    MockSocket,       # Network sockets
    
    # MPOS mocks
    MockTaskManager,  # Async task management
    MockDownloadManager,  # HTTP downloads
    
    # Network mocks
    MockNetwork,      # WiFi/network module
    MockRequests,     # HTTP requests
    MockResponse,     # HTTP responses
    
    # Utility mocks
    MockTime,         # Time functions
    MockJSON,         # JSON parsing
    
    # Helpers
    inject_mocks,     # Inject mocks into sys.modules
    create_mock_module,  # Create mock module
)

Injecting Mocks

from mpos.testing import inject_mocks, MockMachine, MockNetwork

# Inject before importing modules that use hardware
inject_mocks({
    'machine': MockMachine(),
    'network': MockNetwork(connected=True),
})

# Now import the module under test
from mpos.hardware import some_module

Mock Examples

MockNeoPixel:

from mpos.testing import MockNeoPixel, MockPin

pin = MockPin(5)
leds = MockNeoPixel(pin, 10)

leds[0] = (255, 0, 0)  # Set first LED to red
leds.write()

assert leds.write_count == 1
assert leds[0] == (255, 0, 0)

MockRequests:

from mpos.testing import MockRequests

mock_requests = MockRequests()
mock_requests.set_next_response(
    status_code=200,
    text='{"status": "ok"}',
    headers={'Content-Type': 'application/json'}
)

response = mock_requests.get("https://api.example.com/data")
assert response.status_code == 200

MockTimer:

from mpos.testing import MockTimer

timer = MockTimer(0)
timer.init(period=1000, mode=MockTimer.PERIODIC, callback=my_callback)

# Manually trigger for testing
timer.trigger()

# Or trigger all timers
MockTimer.trigger_all()

Test Naming Conventions

  • test_*.py - Standard unit tests
  • test_graphical_*.py - Tests requiring LVGL/UI (detected by unittest.sh)
  • manual_test_*.py - Manual tests (not run automatically)

Writing New Tests

Simple Unit Test

import unittest

class TestMyFeature(unittest.TestCase):
    def test_something(self):
        result = my_function()
        self.assertEqual(result, expected)

Graphical Test

from base import GraphicalTestBase
import lvgl as lv

class TestMyUI(GraphicalTestBase):
    def test_button_click(self):
        button = lv.button(self.screen)
        label = lv.label(button)
        label.set_text("Click Me")
        
        self.wait_for_render()
        self.click_button(button)
        
        # Verify result

Keyboard Test

from base import KeyboardTestBase

class TestMyKeyboard(KeyboardTestBase):
    def test_input(self):
        self.create_keyboard_scene()
        
        self.type_text("hello")
        self.assertTextareaText("hello")
        
        self.click_keyboard_button("Enter")

Test with Mocks

import unittest
from mpos.testing import MockNetwork, inject_mocks

class TestNetworkFeature(unittest.TestCase):
    def setUp(self):
        self.mock_network = MockNetwork(connected=True)
        inject_mocks({'network': self.mock_network})
    
    def test_connected(self):
        from my_module import check_connection
        self.assertTrue(check_connection())
    
    def test_disconnected(self):
        self.mock_network.set_connected(False)
        from my_module import check_connection
        self.assertFalse(check_connection())

Best Practices

  1. Use base classes - Extend GraphicalTestBase or KeyboardTestBase for UI tests
  2. Use mpos.testing mocks - Don't create inline mocks; use the centralized ones
  3. Clean up in tearDown - Base classes handle this, but custom tests should clean up
  4. Don't include if __name__ == '__main__' - The test runner handles this
  5. Use descriptive test names - test_keyboard_q_button_works not test_1
  6. Add docstrings - Explain what the test verifies and why

Debugging Tests

# Run with verbose output
./tests/unittest.sh tests/test_my_test.py

# Run with GDB (desktop only)
gdb --args ./lvgl_micropython/build/lvgl_micropy_unix -X heapsize=8M tests/test_my_test.py

Screenshots

Screenshots are saved to tests/screenshots/ in raw format. Convert to PNG:

cd tests/screenshots
./convert_to_png.sh