You've already forked MicroPythonOS
mirror of
https://github.com/m5stack/MicroPythonOS.git
synced 2026-05-20 11:51:27 -07:00
a50e3722c9
It seems to cause SSL/TLS session corruption on ESP32. There is a performance impact, so maybe it should be reintroduced again later, but for now, let's keep it simple and fix this bug.
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:
-
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 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 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
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 + 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
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 teststest_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
- 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
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