Skip to content
← Production DevOps & SRE

Practical lab guide

Lab: test an operational analysis toolkit

Run offline algorithm fixtures and explain correctness, cost, and production limits.

  • Python
Documentation reviewed2026-10-01 · 4 min read · lab time varies
  1. Why
  2. Predict
  3. Try
  4. Inspect
  5. Explain
  6. Save evidence

Predict before you try

Can a result that works for one small example establish the algorithm or design works at its boundary?

Try it: 1. Prepare the local exercise →
On this page

1. Prepare the local exercise

Save the seven preceding bites' Python listings in .local/advanced-design/ inside your MicroBank checkout, using their documented filenames. The listings are independent exercise utilities, not modifications to the application. Use Python 3.11 or later. No dependency installation or network request is required.

bash
cd .local/advanced-design
python3 --version

Create test_toolkit.py in the same directory:

python
import unittest
from bisect import bisect_left
from random import Random

from complexity import unique_in_order
from events import summarize
from graph_walk import bfs_distances, dfs_reachable
from dependency_order import dependency_order
from topk import top_k_counts
from time_search import lower_bound, interval_indices
from rate_window import SlidingWindow


class ToolkitTests(unittest.TestCase):
    def test_ordered_deduplication(self):
        self.assertEqual(unique_in_order(["b", "a", "b"]), ["b", "a"])
        self.assertEqual(unique_in_order([]), [])

    def test_deliveries_and_distinct_events(self):
        rows = [{"id": "1", "kind": "timeout"}] * 2
        self.assertEqual(summarize(rows), ({"timeout": 2}, {"timeout": 1}, 1))
        self.assertEqual(summarize([]), ({}, {}, 0))
        with self.assertRaises(ValueError):
            summarize([{"id": "", "kind": "timeout"}])
        with self.assertRaises(KeyError):
            summarize([{"id": "1"}])

    def test_cycle_does_not_loop_forever(self):
        graph = {"a": ["b", "c"], "b": ["a", "c"], "c": [], "z": []}
        self.assertEqual(bfs_distances(graph, "a"), {"a": 0, "b": 1, "c": 1})
        self.assertEqual(dfs_reachable(graph, "a"), {"a", "b", "c"})
        with self.assertRaises(ValueError):
            bfs_distances({"a": ["missing"]}, "a")

    def test_precedence_property(self):
        graph = {"db": [], "api": ["db", "db"], "check": ["api"], "docs": []}
        order = dependency_order(graph)
        self.assertEqual(set(order), set(graph))
        position = {task: i for i, task in enumerate(order)}
        self.assertTrue(all(position[p] < position[t]
                            for t, parents in graph.items() for p in parents))
        self.assertEqual(dependency_order({}), [])
        for invalid in ({"a": ["a"]}, {"a": ["b"], "b": ["a"], "c": ["b"]},
                        {"a": ["missing"]}):
            with self.subTest(invalid=invalid), self.assertRaises(ValueError):
                dependency_order(invalid)

    def test_heap_against_full_sort(self):
        rng = Random(41)
        for size in range(20):
            counts = {f"event-{i}": rng.randrange(6) for i in range(size)}
            ranked = sorted(((count, name) for name, count in counts.items()), reverse=True)
            for k in (0, 1, 3, size + 2):
                expected = [(name, count) for count, name in ranked[:k]]
                self.assertEqual(top_k_counts(counts, k), expected)
        with self.assertRaises(ValueError):
            top_k_counts({"timeout": -1}, 1)

    def test_search_against_library_and_linear_filter(self):
        rng = Random(42)
        for size in range(20):
            values = sorted(rng.randrange(10) for _ in range(size))
            for target in range(-1, 12):
                self.assertEqual(lower_bound(values, target), bisect_left(values, target))
            for start, end in ((0, 5), (3, 3), (-1, 11), (7, 9)):
                left, right = interval_indices(values, start, end)
                self.assertEqual(values[left:right], [v for v in values if start <= v < end])
        with self.assertRaises(ValueError):
            interval_indices([], 2, 1)

    def test_window_boundaries(self):
        limiter = SlidingWindow(2, 60)
        self.assertEqual([limiter.allow(t) for t in (0, 1, 2, 60, 60, 61)],
                         [True, True, False, True, False, True])
        self.assertLessEqual(len(limiter.accepted), 2)
        with self.assertRaises(ValueError):
            limiter.allow(59)
        with self.assertRaises(ValueError):
            limiter.allow(float("nan"))

    def test_independent_replicas_have_independent_limits(self):
        replicas = [SlidingWindow(2, 60), SlidingWindow(2, 60)]
        accepted = sum(replica.allow(0) for replica in replicas for _ in range(2))
        self.assertEqual(accepted, 4)  # Not a shared global limit of two.


if __name__ == "__main__":
    unittest.main()

2. Verify behavior, then change a requirement

bash
python3 -m unittest -v test_toolkit

Expected outcome: eight test methods pass. These fixtures check small local algorithms, not a running MicroBank deployment. Tests against a different implementation, such as full sorting or a linear filter, can expose mistakes that assertions copied from the optimized algorithm miss.

Choose one extension: reject conflicting duplicate-event payloads, return a shortest unweighted path as well as distance, or reverse the documented top-k tie rule. Write a failing example for the new behavior before modifying the function. Retain the existing contract tests unless the requirement intentionally changes.

3. Explain the operating boundary

Write exercise-review.md alongside the files. For each utility, state its invariant, time and space bounds, invalid-input behavior, and one deployment limitation. Explain what happens with a file larger than RAM, many distinct keys, multiple workers, or out-of-order timestamps. Streaming input does not by itself bound maps, sets, or exact quantile structures.

These algorithms analyze data or simulate behavior locally. Do not wire graph reachability to automatic remediation. If later consuming real logs, normalize and redact them first; never copy credentials or customer data into practice fixtures.

Checkpoint

Keep the Python version, actual test result, your added edge case, and the review. Use those results to record what you completed. You can now continue to the optional problem set or the system-design bites; no cluster needs to be started yet.

Read a failure productively

An import error usually means a filename or working directory differs from the setup, before any algorithm was tested. An assertion failure names a behavior to compare with the written contract. In the heap test, the full-sort oracle supplies an independent answer; in the graph test, the precedence property allows several correct orders. Do not replace either with a hardcoded output merely to get green tests.

Before moving on, explain one passing edge case without looking at the implementation. Then save a new failing example for your chosen requirement change. The optional checkpoint offers more patterns; it is not a prerequisite for the design review.

Sources

Python unittest↗. Algorithm references and input contracts are linked in the preceding seven bites. All fixtures and test cases here are newly authored for this path.

Prove what you learned

Inspect the result

Retain input/assumptions, expected result, actual trace and complexity or design limits.

Explain it in your own words

Explain why your observation supports the stated outcome, and name one thing it does not prove.

Try a small variation

Change one boundary condition—empty input, duplicate value, interval edge or missing dependency—and explain the outcome.

Your evidence should show

  • Record the context/revision or label the work as a paper or prepared fixture.
  • Compare the prediction with the actual result from “1. Prepare the local exercise”.
  • Retain input/assumptions, expected result, actual trace and complexity or design limits.
  • Explain the transfer challenge and retain the same verification question.
Hint

State the input assumptions and trace an empty, repeated or limiting case.

Answer guide: compare after predicting

No. Explain correctness and cost or the design trade-off, and compare a declared boundary case.

Compare this with your actual observation. A paper answer is useful reasoning practice; record execution separately.

Recall bounded loops, data types and explicit failure handling in Python. Revisit the previous lesson.

Save the prediction, result and explanation ↓

Your notes and evidence

Record observations, questions, or links to your work. Keep credentials out of your notes.

Loading saved progress…

Back up or restore this path

Progress and notes stay in this browser. A backup contains only this learning path.