This directory contains the test suite for MicroPythonOS. Tests can run on both desktop (for fast iteration) and on-device (for hardware verification).
# 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 --ondevicetests/
├── 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
MicroPythonOS provides two testing modules:
-
mpos.testing- Hardware and system mocks- Location:
internal_filesystem/lib/mpos/testing/ - Use for: Mocking hardware (Pin, PWM, I2S, NeoPixel), network, async operations
- Location:
-
mpos.ui.testing- LVGL/UI testing utilities- Location:
internal_filesystem/lib/mpos/ui/testing.py - Use for: UI interaction, screenshots, widget inspection
- Location:
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 taskscapture_screenshot(filename)- Save screenshotfind_label_with_text(text)- Find label widgetclick_button(button)- Simulate button clickassertTextPresent(text)- Assert text is on screenassertWidgetVisible(widget)- Assert widget is visible
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 + MposKeyboardclick_keyboard_button(text)- Click keyboard button reliablytype_text(text)- Type a stringget_textarea_text()- Get textarea contentclear_textarea()- Clear textareaassertTextareaText(expected)- Assert textarea contentassertTextareaEmpty()- Assert textarea is empty
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
)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_moduleMockNeoPixel:
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 == 200MockTimer:
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_*.py- Standard unit teststest_graphical_*.py- Tests requiring LVGL/UI (detected by unittest.sh)manual_test_*.py- Manual tests (not run automatically)
import unittest
class TestMyFeature(unittest.TestCase):
def test_something(self):
result = my_function()
self.assertEqual(result, expected)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 resultfrom 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")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())- Use base classes - Extend
GraphicalTestBaseorKeyboardTestBasefor UI tests - Use mpos.testing mocks - Don't create inline mocks; use the centralized ones
- Clean up in tearDown - Base classes handle this, but custom tests should clean up
- Don't include
if __name__ == '__main__'- The test runner handles this - Use descriptive test names -
test_keyboard_q_button_worksnottest_1 - Add docstrings - Explain what the test verifies and why
# 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.pyScreenshots are saved to tests/screenshots/ in raw format. Convert to PNG:
cd tests/screenshots
./convert_to_png.sh