budtestlibrary
Universal Python test automation framework for HIL, SIL, web, mobile, cloud, API, security, performance, and end-to-end testing.
It provides lifecycle management, rich assertions, structured results, logging,
firmware-flashing abstractions, and optional Bloom PLM integration through
BloomMetaData attached to test classes.
Creator: Amine El Omari
Requirements
- Python 3.9 or later
- No required runtime dependencies
Installation
python -m pip install budtestlibrary
Features
BudTestCaselifecycle withsetUpClass()andtearDownClass().- Automatic discovery of methods whose names start with
bud_. - Boolean, equality, membership, range, tolerance, and regex assertions.
- Structured assertion and test-method results.
- Configurable source-location, traceback, and value capture.
- Coloured console output and plain serialised result data.
- Firmware flashing through
FlashEvent,FlashSuccess, andFlashFailure. - Environment-variable and
app.propertiesconfiguration. - Bundled examples for HIL, SIL, API, UI, cloud/E2E, and flashing scenarios.
- Optional Bloom PLM traceability with
BloomMetaData.
Quick start
import logging
from budtestlibrary import BloomMetaData, BudTestCase
class MyTest(BudTestCase):
bloom_metadata = BloomMetaData("PRJ", "001") # Optional: attach Bloom traceability metadata
def setUpClass(self):
self.log_info("Setting up test")
def bud_check_response(self):
response = get_response()
self.assertTrue(response.ok, msg="Response is successful")
def bud_validate_output(self):
result = compute_result()
self.assertInTolerance(
result,
expected=42.0,
absolute_tolerance=0.5,
msg="Output is within tolerance",
)
def tearDownClass(self):
self.log_info("Tearing down test")
if __name__ == "__main__":
test = MyTest()
test.set_loglevel(logging.INFO)
test.run()
Optional Bloom Traceability
BloomMetaData optionally links a test class to a Bloom test case using the
{Project}-TC-{ID} convention:
from budtestlibrary import BloomMetaData, BudTestCase
class TraceableTest(BudTestCase):
bloom_metadata = BloomMetaData("PRJ", "001")
This integration is optional. Tests run normally without Bloom metadata or a
Bloom deployment. When results flow through bud_runner into Bud, Bud uses the
metadata when the corresponding Bud project is linked to Bloom.
Checking what a suite claims
A tc_id is typed by hand. If it is wrong, Bloom reports it as unmatched, Bud
records a warning on the run, and nothing else happens — the run is green and the
test case reads as never executed. If two classes claim the same id, Bud coalesces
them into one result and one outcome silently replaces the other.
budtestlibrary check tests/
It imports the modules under that path, reads the id each test class claims, and reports duplicates, classes claiming nothing, and modules it could not import. It exits non-zero on a duplicate, so CI fails on the error rather than on a report nobody reads.
✗ FLT-TC-001 is claimed by 2 test classes:
tests/test_brakes.py::BrakePressureTest
tests/test_thermal.py::ThermalSoakTest
2 test class(es): 2 claim 1 Bloom test case(s), 0 claim none, 1 id(s) duplicated.
--list prints the tc_id to test class map, --json emits the same report for a
pipeline, and --strict also fails when a test class claims no Bloom test case.
The command talks to nothing. It cannot tell whether FLT-TC-001 exists in Bloom,
because this library holds no Bloom address and no credential.
Public API
| Export | Purpose |
|---|---|
BudTestCase |
Base class for lifecycle, assertions, logging, and results |
BloomMetaData |
Optional Bloom PLM test-case traceability metadata |
FlashEvent |
Abstract firmware-flashing operation |
FlashSuccess |
Successful flashing result |
FlashFailure |
Failed flashing result with error information |
BudConfig |
Configuration loaded from environment and properties |
get_default_config() |
Shared lazy-loaded configuration instance |
budtestlibrary check |
Command that reports the Bloom test cases a suite claims |
Test structure
BudTestCase.run() discovers methods prefixed with bud_ and executes them in
alphabetical order. Prefix methods numerically when explicit ordering matters:
class OrderedTest(BudTestCase):
def bud_01_connect(self):
...
def bud_02_measure(self):
...
After execution, call get_results():
test = MyTest()
test.run()
for method_result in test.get_results():
print(method_result.method_name, method_result.passed)
Assertions
Available helpers include:
assertTrue/assertFalseassertEqual/assertNotEqualassertGreater/assertLessassertIn/assertNotInassertRegexassertInToleranceassertInRangeskipAssert
Example:
self.assertInRange(
actual=temperature,
lower_bound=18.0,
upper_bound=26.0,
include_bounds=True,
msg="Temperature is inside the accepted range",
)
Result capture
Subclass attributes control serialised result size and detail:
| Attribute | Default | Purpose |
|---|---|---|
CAPTURE_SOURCE_PATH |
True |
Capture failure source file and line |
CAPTURE_TRACEBACK |
True |
Capture traceback text |
MAX_RESULT_VALUE_LENGTH |
5000 |
Truncate long expected/actual/result values |
class CompactResultsTest(BudTestCase):
CAPTURE_SOURCE_PATH = False
CAPTURE_TRACEBACK = False
MAX_RESULT_VALUE_LENGTH = 500
Firmware flashing
Implement FlashEvent for product-specific flashing:
from budtestlibrary import FlashEvent, FlashSuccess
class MyFlashEvent(FlashEvent):
DEFAULT_ADDR = 0x08000000
def flash(self, firmware_path, addr=None):
perform_flash(firmware_path, self.DEFAULT_ADDR if addr is None else addr)
return FlashSuccess(message="Flashed successfully")
def get_project_name(self):
return "SensorHub"
def get_firmware_version(self):
return "2.1.0"
def get_release(self):
return "production"
addr is the optional target memory address (for example 0x08000000 on
STM32, 0x10000 on ESP32). Pass it through execute() when a run needs a
specific address, and omit it to use the implementation's default:
event = MyFlashEvent()
event.execute("firmware.bin") # implementation default
event.execute("bootloader.bin", addr=0x08000000) # explicit address
Implementations that do not need an address may keep the single-argument
flash(self, firmware_path) signature — execute() detects this and calls
them unchanged. Passing an explicit addr to such an implementation returns a
FlashFailure explaining that addr=None must be added to its signature.
Configuration
export BUD_BACKEND_URL="https://<your-bud-instance-url>"
export BUD_TOKEN="<user-token>"
budBackend=https://<your-bud-instance-url>
budRunnerAccount=lab-station-01
Keep secrets outside repositories.
Bundled examples
Examples ship inside the wheel under budtestlibrary.examples.
python -c "import budtestlibrary.examples, pathlib; print(pathlib.Path(budtestlibrary.examples.__file__).parent)"
| Example | Scenario |
|---|---|
minimal_test.py |
Minimal test with core assertions |
bloom_metadata_test.py |
Optional Bloom traceability |
flash_event_example.py |
Firmware flashing |
hil_test.py |
Hardware-in-the-loop |
sil_test.py |
Software-in-the-loop |
api_testing_example.py |
API testing |
ui_testing_example.py |
UI testing |
cloud_e2e_example.py |
Cloud and E2E testing |
Compatibility
budtestlibrary |
Intended bud_runner pairing |
Notes |
|---|---|---|
1.0.3 |
1.0.3 |
Optional addr target address on FlashEvent.flash() and FlashEvent.execute() |
1.0.2 |
1.0.2 |
Permanent AGPL wording clarified; examples and README coverage expanded |
1.0.1 |
1.0.1 |
Examples bundled in the wheel |
1.0.0.post2 |
1.0.0.post2 |
Configurable capture, flashing abstractions, and separated test-software metadata |
Development
git clone https://github.com/MbedLabs/bud-test-library.git
cd bud-test-library
python -m pip install -e ".[dev]"
black --check budtestlibrary/ examples/
isort --profile black --check-only budtestlibrary/ examples/
ruff check budtestlibrary/ examples/
mypy budtestlibrary/
pytest tests/ -v
Related packages
- bud_runner: CLI tool for test execution and Bud integration.
- pybudgui: Python Qt desktop client for manual test execution, planned on the roadmap.
Licence
budtestlibrary is permanent free and open-source software licensed under the
GNU Affero General Public License v3.0 only (AGPL-3.0-only).
No paid EmbedLabs licence is required to use budtestlibrary, including for
commercial use, provided the AGPL terms are followed. Accepted community
contributions remain publicly available under AGPL-3.0-only and will not
become proprietary-only.
Bud and Bloom are separate source-available applications. Commercial licensing,
deployment, integration, and support offered through sales@embedlabs.de
applies to those applications and services—not to the budtestlibrary package
licence.
Technical, security, and contribution questions: dev@embedlabs.net.
Copyright (C) 2026 Mohamed Amine El Omari Alaoui, operating under the name EmbedLabs.
Metadata
Release files for budtestlibrary 1.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| budtestlibrary-1.1.0.tar.gz | 50.0 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| budtestlibrary-1.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 89.1 kB
Release files / budtestlibrary-1.1.0.tar.gz
| Download URL | budtestlibrary-1.1.0.tar.gz |
|---|---|
| Size | 50.0 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
177dbe0563e92cc2f614f1ecfced4df5c9007bffe598e062db8cd64d8759f5b6
|
|
BLAKE2b-256 checksum How to use checksums |
23fae956b1e97db2aa8ae3b8fd05781098c3f1fa1813d520ed8cb1e84103792f
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 11, 2026.
Transparency logRelease files / budtestlibrary-1.1.0-py3-none-any.whl
| Download URL | budtestlibrary-1.1.0-py3-none-any.whl |
|---|---|
| Size | 39.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
4cce2e2e6e3632ab9cc984ac95b745a526a8a8c15b8fd9cb5094fb73b5d3ef03
|
|
BLAKE2b-256 checksum How to use checksums |
6eef8e28c0495daf7347aff066b3fd1c9f8105bad287a7c337fa6d313cfe8d60
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 11, 2026.
Transparency log