Skip to main content

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

  • BudTestCase lifecycle with setUpClass() and tearDownClass().
  • 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, and FlashFailure.
  • Environment-variable and app.properties configuration.
  • 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 / assertFalse
  • assertEqual / assertNotEqual
  • assertGreater / assertLess
  • assertIn / assertNotIn
  • assertRegex
  • assertInTolerance
  • assertInRange
  • skipAssert

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
  • 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)

Source distribution for budtestlibrary 1.1.0
File Size Uploaded
budtestlibrary-1.1.0.tar.gz 50.0 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for budtestlibrary 1.1.0
File Interpreter ABI Platform
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 log

Release 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

Release history Release notifications | RSS feed

This release

1.1.0 This release

2 release files

1.0.3

2 release files

1.0.2

2 release files

1.0.1

2 release files

1.0.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page