aiml
SearchMy learning
Menu

Lesson 12 of 21Module 4

Test pipeline changes before producing a candidate

Run unit and integration checks through one local CI command, retain discovered test counts and diagnostics, and propagate a failed result.

Prerequisites

Complete the preceding course lessons in the shared user-return lab; keep their work/ outputs. Python functions and local CLI commands.

Preparation and term definitions

By the end, you can

  • Separate code and contract tests from candidate quality gates.
  • Retain test counts, diagnostics and failing exit status.
  • Distinguish a locally executed CI command from an unrun hosted workflow.
Runtime
Python 3.12+; local CPU
Dependencies
Pinned MLflow, Prefect and FastAPI dependencies in requirements-platform.txt

Lab code, tests and fixtures (ZIP, 1.6 MB)Quiz check your understanding

Progress is saved in this browser.

Run the application’s failure and integration checks from one command that can also serve as a repository CI job.

Problem

When you need this. A change to arrival-time filtering can leak future events even when fitting still completes. A change to packaging can leave a model that no prediction process can load. The pipeline therefore needs checks for its data and artifact contracts, not just a successful training command.

This workshop combines the existing tests with the new local platform integrations. A single command returns a machine-readable result and a nonzero exit status when a check fails.

Concepts

Continuous integration runs checks when code changes. It answers whether the change satisfies the checks included in that job. Model evaluation asks how a candidate performs on an intended population; passing unit tests cannot answer that question.

The suite covers strict JSON and event availability, temporal splits, immutable identities, actual MLflow artifacts, Prefect retry behavior, registry selection and prediction validation. Temporary local databases keep one test run from depending on old state.

The included ci.yml is an optional GitHub Actions workflow for a repository containing the extracted lab at its root. It installs the same pinned environment and calls the same checker. The local command is executed here; the hosted workflow has not been dispatched.

Architecture

Application changescode + fixturesRun local checksunit + integrationSuccessful result55 checks in this runFailed checknonzero process exitApplication changescode + fixturesRun local checksunit + integrationSuccessful result55 checks in this runFailed checknonzero process exit
The same local checker can run inside a repository CI job. These tests check program behavior; the candidate gate evaluates the fitted model.

Working code

Download the shared application and tests, unzip it and enter mlops-return-workflow/. Keep the same directory throughout the course. Create the environment once. Install the pinned platform dependencies for this workshop.

Create the local environmentTerminal · bash
python3.12 -m venv .venv
source .venv/bin/activate
python -m pip install -r requirements-platform.txt

On PowerShell, activate with .venv\Scripts\Activate.ps1.

Continue from Retry a failed step without repeating the experiment; keep the same lab directory and outputs.

Make check results usable by automation

The checker records test counts and returns the test runner’s success status. Failed tests remain visible in the text output.

Source excerpt: checkSource · pythonDownload
def check():
    names=['test_workflow','test_foundations','test_tracking','test_platform']
    suite=unittest.defaultTestLoader.loadTestsFromNames(names)
    stream=io.StringIO();result=unittest.TextTestRunner(stream=stream,verbosity=2).run(suite)
    print(stream.getvalue(),file=sys.stderr)
    return {'command':'python ci.py','tests_run':result.testsRun,'failures':len(result.failures),
            'errors':len(result.errors),'skipped':len(result.skipped),'passed':result.wasSuccessful(),
            'python':sys.version.split()[0],'packages':{p:importlib.metadata.version(p) for p in ['mlflow-skinny','prefect']},
            'execution':'actual local tests; hosted GitHub Actions job not executed'}
Source excerpt: mainSource · pythonDownload
def main():
    p=argparse.ArgumentParser();p.add_argument('--out',type=Path,required=True);a=p.parse_args()
    result=check();w.save(a.out,result);print(json.dumps(result,indent=2));sys.exit(0 if result['passed'] else 1)


if __name__=='__main__':main()
Make check results usable by automationTerminal · bash
python course.py ci-checks --workspace work/course
Actual local application outputRecorded output · jsonDownload
{
  "command": "python ci.py",
  "tests_run": 55,
  "failures": 0,
  "errors": 0,
  "skipped": 0,
  "passed": true,
  "python": "3.12.8",
  "packages": {
    "mlflow-skinny": "3.17.2",
    "prefect": "3.9.0"
  },
  "execution": "actual local tests; hosted GitHub Actions job not executed"
}

The JSON result records the actual installed dependency versions as run metadata. It reports what this local test process executed, without claiming a hosted CI run.

Keep independent checks around the integration

The cohort and registry tests deliberately violate their contracts. They check effects such as an absent candidate or an unchanged selected version, rather than merely mirroring a function’s return value.

Source excerpt: ci_reportSource · pythonDownload
def ci_report(root):
    import ci
    need(root,'retry-state.json');result=ci.check()
    if not result['passed']:raise ValueError('local CI checks failed; inspect raw test output')
    return result

Read the named failure tests in test_platform.py alongside the earlier test_workflow.py and test_foundations.py. The downloaded lab contains all of them.

Recorded commands retain outputs in recorded-platform/; your commands use work/. Read the actual command records. Use a fresh work directory for another attempt; original outputs are preserved.

Failure cases

A green job that never discovers tests is not useful. The retained result includes tests_run, failures, errors and skipped; inspect these fields when changing discovery.

The suite deliberately produces failed Prefect and MLflow runs to verify their boundaries. Their expected failure logs do not mean the overall suite failed; the final test summary determines its exit status.

Run the application failure checksTerminal · bash
python -m unittest -v test_platform.py

Running and scaling the workshop

Use python ci.py –out work/ci-result.json for the equivalent direct check. Put ci.yml under .github/workflows/ only when adopting this lab as its own repository. Hosted execution is optional and unverified here.

Keep remote credentials out of this job. It uses synthetic data, local CPU fitting and temporary stores. Larger projects can split fast contract tests from slower integrations while retaining both before release.

Key takeaways

  • CI checks code and contracts; candidate gates check evaluated artifacts.
  • A useful job preserves diagnostics and propagates failures.
  • A workflow file does not show that a hosted job ran.

Best practices

Test time rules and artifact loading, not only the training return code.

Keep the same entry point for local and repository checks.

Check your understanding

Can passing these tests replace candidate-quality evaluation?

Show the explanation

No. They check program behavior on known cases. The next workshop evaluates the fitted candidate against its declared validation policy.

The CI command exits zero. What else should you inspect before trusting its test result?

Choose one answer, then check your reasoning.

Read the explanation

Inspect discovered test count, failures, errors, skips and exit status. A zero exit without discovered checks cannot establish that the intended suite ran.

References

Written by Arjun Prabhulal.

Module 4

Automate and test the workflow