Skip to content
GitHub
documentation

budtestlibrary

Write test cases with budtestlibrary, check their Bloom metadata, and tune what results capture.

A universal test framework with lifecycle management, rich assertions, logging, and Bloom integration: covering HIL, SIL, Web, Mobile, Cloud, and E2E testing. Subclass BudTestCase and write your checks as bud_* methods. Optionally attach BloomMetaData for traceability.

budtestlibrary on PyPI · budtestlibrary on GitHub

pip install budtestlibrary

BudTestCase.run() discovers bud_* methods in alphabetical order. Use numeric prefixes such as bud_01_connect and bud_02_measure when order matters. After execution, get_results() returns the method results, including each method’s name and pass status.

import logging
from budtestlibrary import BudTestCase, BloomMetaData
class TraceableTest(BudTestCase):
bloom_metadata = BloomMetaData("PRJ", "001") # optional Bloom traceability
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):
self.assertInTolerance(compute(), expected=42.0, absolute_tolerance=0.5)
def tearDownClass(self):
self.log_info("Tearing down test...")
if __name__ == "__main__":
test = TraceableTest()
test.set_loglevel(logging.INFO)
test.run()

To run test cases through Bud, list them in a module attribute and pass it to bud_runner run-tests. See Getting started.

Check the test case IDs a suite claims before uploading results. This command imports test modules, reports duplicate IDs, missing metadata, and import errors, and exits non-zero on duplicates. Use --strict to also fail on missing metadata, --list to show the ID-to-class map, or --json for a machine-readable report.

budtestlibrary check tests/

Use trusted test modules: importing them can execute Python code. This check does not run the test suite or contact Bud or Bloom, so it cannot confirm that an ID exists in Bloom.

To send outcomes for these IDs to Bloom, see Connecting to Bloom.

assertTrue / assertFalse, assertEqual / assertNotEqual, assertGreater / assertLess, assertIn / assertNotIn, assertRegex, and skipAssert, plus two numeric checks built for measurement:

self.assertInTolerance(actual, expected,
absolute_tolerance=0.1, # ±0.1
relative_tolerance=0.05, # ±5%
msg="within range")
self.assertInRange(actual, lower_bound=0.0, upper_bound=10.0,
include_bounds=True, msg="within range")

Each bud_* method produces a TestMethodResult (method name, pass/skip, assertions, duration_seconds, error and summary messages). Each assertion produces a TestResult with expected, actual, source location, and traceback. bud_runner flattens these into the payload it uploads to Bud TMP. Tune capture with class attributes:

class CompactResultsTest(BudTestCase):
CAPTURE_SOURCE_PATH = False # omit source file/line/function
CAPTURE_TRACEBACK = False # omit tracebacks
MAX_RESULT_VALUE_LENGTH = 500 # truncate long expected/actual [default: 5000]

For firmware workflows, model programming outcomes with FlashEvent, FlashSuccess, and FlashFailure. FlashEvent.execute() takes a firmware_path:

class MyFlashEvent(FlashEvent):
def flash(self, firmware_path, addr=None):
...
return FlashSuccess()
result = MyFlashEvent().execute("/path/to/firmware.hex")

Subclass FlashEvent for product-specific flashing. Both flash() and execute() take an optional addr target memory address: 0x08000000 on STM32, 0x10000 on ESP32, and None means the implementation’s own default.

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 "Demo"
event = MyFlashEvent()
event.execute("firmware.bin") # implementation default
event.execute("bootloader.bin", addr=0x08000000) # explicit address

Implementations that need no address may keep the single-argument flash(self, firmware_path) signature: execute() detects that and calls them unchanged, so existing subclasses keep working. Passing an explicit addr to one of those returns a FlashFailure naming the class and the signature change it needs.

export BUD_BACKEND_URL="https://bud.example.com/"
export BUD_TOKEN="your-api-token"
# or app.properties: budBackend=... budRunnerAccount=station-01

Keep tokens out of app.properties: it is for project metadata only. See Identity and secrets.

The source distribution ships runnable examples for every domain: minimal_test.py, bloom_metadata_test.py, flash_event_example.py, hil_test.py, sil_test.py, api_testing_example.py, ui_testing_example.py, and cloud_e2e_example.py.