Testing Guide: mycroft-gui-qt6
March 12, 2026 · View on GitHub
How to test mycroft-gui-qt6 changes and contributions
Test Suite Overview
The project includes 5 automated test executables covering:
- Message routing (protocol compliance)
- QML component loading
- Server functionality
- Model data handling
- Stress testing
All tests are built with CMake and run via ctest.
Running Tests
Quick Test (30 seconds)
cd build
make test
# Expected output:
# Test project /path/to/build
# Start 1: servertest
# 1/5 Test #1: servertest ........................ PASSED 0.05 sec
# Start 2: modeltest
# 2/5 Test #2: modeltest ........................ PASSED 0.03 sec
# ...
# 100% tests passed, 0 failures out of 5
Verbose Test (with output)
make test VERBOSE=1
# Or
ctest --output-on-failure
Run Individual Tests
# Test WebSocket protocol compliance
./autotests/message_routing_test
# Test QML can load all components
./autotests/qml_framework_components_test
# Test server functionality
./autotests/servertest
# Test model data handling
./autotests/modeltest
# Stress test under load
./autotests/stresstest
Test Descriptions
1. message_routing_test
Purpose: Verify WebSocket protocol compliance
Tests:
- ✅ All 23 OVOS message types recognized
- ✅ Unknown messages handled gracefully
- ✅ Message categorization correct
- ✅ Protocol enum conversion working
Code location: autotests/message_routing_test.cpp
Time: ~0.02s
When to run:
- After changes to
GuiBusMessages::fromString() - After modifying message parsing logic
- When updating protocol
2. qml_framework_components_test
Purpose: Verify all QML components can be loaded
Tests:
- ✅ All 17 framework components loadable
- ✅ QML_ELEMENT registrations working
- ✅ Qt version validation passes
- ✅ No deprecated warnings
Code location: autotests/qml_framework_components_test.cpp
Time: ~0.04s
When to run:
- After modifying QML registration
- After updating component imports
- When adding new QML components
3. servertest
Purpose: Test core server functionality
Tests:
- ✅ Server can start and stop
- ✅ Connections accepted
- ✅ Messages routed correctly
- ✅ Session data managed properly
Code location: autotests/servertest.cpp
Time: ~0.05s
When to run:
- After changes to MycroftController
- After modifying WebSocket handling
- When refactoring message routing
4. modeltest
Purpose: Test model data handling
Tests:
- ✅ ActiveSkillsModel updates correctly
- ✅ SessionDataModel handles data
- ✅ Model signals emit properly
- ✅ Data bindings work
Code location: autotests/modeltest.cpp
Time: ~0.03s
When to run:
- After modifying model classes
- After updating data structures
- When changing property bindings
5. stresstest
Purpose: Test under load and stress conditions
Tests:
- ✅ Many connections handled
- ✅ High message throughput
- ✅ Memory stable under load
- ✅ No deadlocks or hangs
Code location: autotests/stresstest.cpp
Time: ~0.12s
When to run:
- Before release
- After performance-critical changes
- When debugging mysterious crashes
Build with Testing
Enable Tests
# Configure build with testing enabled
cmake .. -DBUILD_TESTING=ON -DCMAKE_BUILD_TYPE=Release
# Or Debug for better error messages
cmake .. -DBUILD_TESTING=ON -DCMAKE_BUILD_TYPE=Debug
Build Tests
make -j$(nproc)
# Or just build tests without building main library
make servertest modeltest stresstest message_routing_test qml_framework_components_test
Test-Driven Development
When Adding a Feature
-
Write the test first
// autotests/myfeature_test.cpp void MyFeatureTest::testNewFeature() { QVERIFY(controller.newFeature() == expectedValue); } -
Run the test (it fails)
./autotests/myfeature_test # FAIL: Feature not implemented -
Implement the feature
// import/mycroftcontroller.cpp bool MycroftController::newFeature() { return true; } -
Run the test (it passes)
./autotests/myfeature_test # PASS -
Run full test suite
cd build && make test # All 5+ tests pass
When Fixing a Bug
-
Create a test that reproduces the bug
void BugFixTest::testBugReproduced() { // This test should fail with the bug present QVERIFY(controller.buggyBehavior() == false); } -
Run the test (it fails with the bug)
./autotests/bugfix_test # FAIL: Shows the bug is present -
Fix the bug
// Fix the implementation -
Run the test (it passes)
./autotests/bugfix_test # PASS: Bug is fixed -
Keep the test
- Prevents regression
- Documents the bug fix
Memory Testing
Detect Memory Leaks
Build with AddressSanitizer:
cd build
cmake .. -DCMAKE_CXX_FLAGS="-fsanitize=address" -DBUILD_TESTING=ON
make clean && make -j$(nproc)
make test VERBOSE=1
Look for:
SUMMARY: AddressSanitizer: 0 bytes leaked
✅ Pass: No memory leaks detected
❌ Fail: SUMMARY shows bytes leaked
Detect Use-After-Free
The same AddressSanitizer build catches use-after-free bugs:
ERROR: AddressSanitizer: heap-use-after-free
Detect Stack Overflows
Build with Stack Protector:
cmake .. -DCMAKE_CXX_FLAGS="-fstack-protector-all"
make test
Performance Testing
Measure Build Time
time make -j$(nproc)
# Expected: ~2-5 minutes depending on CPU cores
Measure Runtime Performance
# Run stresstest to completion
time ./autotests/stresstest
# Check QML rendering FPS (if display available)
mycroft-gui-app --profile-qml
Profile with perf
If linux-tools is installed:
perf record ./autotests/stresstest
perf report
Continuous Integration (CI)
Before Pushing
Always run this locally:
cd build
cmake .. -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTING=ON
make clean
make -j$(nproc)
make test VERBOSE=1
# All tests should pass
GitHub Actions
Tests run automatically on:
- ✅ Every push to dev branch
- ✅ Every pull request
- ✅ Once per day (schedule)
Check CI status:
# Show latest CI runs
gh run list
# View specific run
gh run view <run-id>
# View logs
gh run view <run-id> --log
Writing New Tests
Test File Template
Create autotests/myfeature_test.cpp:
#include <QtTest/QtTest>
#include <QObject>
// Include the class you're testing
#include "../import/mycroftcontroller.h"
class MyFeatureTest : public QObject {
Q_OBJECT
private slots:
// Each test_ method is a separate test
void initTestCase() {
// Run once before all tests
// Set up shared resources
}
void cleanupTestCase() {
// Run once after all tests
// Clean up shared resources
}
void testFeatureBasic() {
MycroftController controller;
// Test basic functionality
QVERIFY(controller.feature() == true);
}
void testFeatureWithData() {
MycroftController controller;
// Test with specific data
QCOMPARE(controller.getValue(), 42);
}
void testFeatureFailsGracefully() {
MycroftController controller;
// Test error handling
QVERIFY_EXCEPTION_THROWN(
controller.badCall(),
std::runtime_error
);
}
};
// Required to generate test executable
QTEST_MAIN(MyFeatureTest)
#include "myfeature_test.moc"
Add to CMakeLists.txt
In autotests/CMakeLists.txt:
ecm_add_test(
myfeature_test.cpp
${import_SRCS}
TEST_NAME myfeature_test
LINK_LIBRARIES
Qt6::Test
Qt6::Qml
Qt6::Quick
Qt6::Network
Qt6::WebSockets
)
Common Test Assertions
QVERIFY(condition) // Assert true
QVERIFY2(condition, message) // With message
QCOMPARE(actual, expected) // Equality
QCOMPARE(actual, expected, ...) // Multiple assertions
QTEST_COMPARE(a, b) // Using == operator
QTEST_COMPARE_LT(a, b) // Using < operator
QFAIL(message) // Force failure
QWARN(message) // Print warning
QSKIP(reason) // Skip this test
QVERIFY_EXCEPTION_THROWN(code, exception) // Exception testing
Signal Testing
void TestMySignals::testSignalEmitted() {
MycroftController controller;
// Verify signal is emitted
QSignalSpy spy(&controller, &MycroftController::connectedChanged);
controller.connect();
// Check signal was emitted once
QCOMPARE(spy.count(), 1);
}
Debugging Test Failures
Print Debug Information
void MyFeatureTest::testDebug() {
int value = controller.getValue();
// Print for debugging
qDebug() << "Value is:" << value;
QCOMPARE(value, 42);
}
Run with output:
./autotests/myfeature_test -v2
Use Qt Creator's Test Debugger
# If using Qt Creator
# 1. Open project
# 2. Run → Run Tests
# 3. Click on failed test
# 4. Debug
Run Single Test
# Run only one test method
./autotests/myfeature_test MyFeatureTest::testSpecific
# Run with extra verbosity
./autotests/myfeature_test -v2
Test Coverage
Check Coverage
cd build
cmake .. -DCMAKE_CXX_FLAGS="--coverage" -DBUILD_TESTING=ON
make clean && make -j$(nproc)
make test
# Generate coverage report
lcov --directory . --capture --output-file coverage.info
lcov --remove coverage.info '*/test*' --output-file coverage.info
genhtml coverage.info --output-directory coverage_report
# View report
open coverage_report/index.html
Coverage Goals
- ✅ Core classes (MycroftController, AbstractSkillView): 80%+
- ✅ Message routing: 90%+ (critical path)
- ⚠️ QML: Testing framework constraints limit coverage
- ✅ Error paths: 100% (catch all failures)
Common Issues & Solutions
Test Fails: "Cannot find Qt6"
Solution:
# Ensure Qt6 is installed
qmake6 -v
# Update CMake cache
cd build
rm CMakeCache.txt
cmake .. -DCMAKE_BUILD_TYPE=Debug -DBUILD_TESTING=ON
make test
Test Fails: "Symbol not found"
Solution:
# Rebuild everything
cd build
make clean
cmake ..
make -j$(nproc)
make test
Test Timeout
Solution:
# Increase timeout (some systems are slow)
ctest --timeout 120 --output-on-failure
Flaky Test (passes sometimes)
Problem: Test has timing-dependent behavior
Solution:
-
Add explicit waits:
QTest::qWait(100); // Wait 100ms -
Use QSignalSpy instead of busy-waiting:
QSignalSpy spy(&obj, &MyObject::ready); spy.wait(5000); // Wait for signal
Best Practices
✅ Do:
- Run tests before committing
- Write tests for new features
- Keep tests fast (< 1s each)
- Test both success and failure paths
- Use meaningful assertion messages
❌ Don't:
- Use
sleep()in tests (use QTest::qWait or QSignalSpy) - Create external files in tests (use temp directories)
- Rely on specific timing (add small delays)
- Skip tests without a good reason
- Test implementation details (test behavior instead)
Next Steps
- Read CODE_GUIDE.md: Understand how to write Qt/C++ code
- Check CONTRIBUTING.md: Guidelines for pull requests
- Look at COMPONENTS.md: API reference for what to test
Happy testing! 🧪