Follow the data across a boundary
import csv
from decimal import Decimal
from io import StringIO
def parse_holdings(text: str) -> list[tuple[str, int, Decimal]]:
"""Convert the fixture's named CSV fields into holdings."""
reader = csv.DictReader(StringIO(text))
return [(r["instrument"], int(r["quantity"]), Decimal(r["price_usd"]))
for r in reader]
def value_holdings(rows: list[tuple[str, int, Decimal]]) -> Decimal:
"""Value shares using USD-per-share prices."""
return sum((quantity * price for _, quantity, price in rows), Decimal("0"))
def test_csv_to_valuation() -> Decimal:
"""Exercise ingestion, conversion and valuation together."""
text = "instrument,quantity,price_usd\nA,2,100\nB,3,50\n"
rows = parse_holdings(text)
assert rows == [("A", 2, Decimal("100")), ("B", 3, Decimal("50"))]
total = value_holdings(rows)
assert total == Decimal("350")
return total
print(f"USD {test_csv_to_valuation():.2f}")
# USD 350.00DictReader supplies text fields; int and Decimal perform the conversions here. The annotations document the interface and do not enforce it at runtime. Decimal values constructed from these integer strings make the displayed arithmetic exact. The intermediate assertion checks the records as well as the final total: a correct aggregate alone can hide offsetting mistakes.This example assumes the named columns, integer share quantities and finite USD prices. A production ingestion contract needs explicit decisions and tests for missing fields, malformed values, duplicates, currency, timestamps and any permitted short positions. A passing happy-path example does not establish those behaviours.
Unit, integration and end-to-end scope
Teams draw these boundaries differently. A useful convention is:
| Test scope | What it exercises in this example |
|---|---|
| Unit test | Valuation logic supplied with already constructed holdings, without CSV ingestion. |
| Integration test | CSV reading, conversion and valuation connected as shown above. |
| End-to-end test | A complete defined workflow, such as uploading a file through an application and checking its resulting report. |
An end-to-end test can include several integrations; the labels do not form a universally fixed taxonomy. Name the included components, entry point, output and replaced dependencies so readers know what the test actually demonstrates.
350 would remove the calculation too. Such replacements may be useful for other tests, but they provide no evidence that the replaced components integrate correctly.Read a pass within its limits
This test checks an in-memory path. It says nothing about file permissions, text encoding at a file boundary, database drivers, network authentication or market-data freshness. Add tests in the relevant environment when those contracts matter. Keep setup controlled and restore state between cases so one test's changes cannot determine another's result.
Sources
- Microsoft Learn: Testing in .NET — Test types, for component-level and integration scope.
- Microsoft Learn: Introduction to integration tests, for collaborating components, infrastructure and controlled test configuration. Its implementation examples concern ASP.NET Core.
- Python 3.14:
csv, especiallyreaderandDictReader, for field mapping and conversion behaviour. - Python 3.14:
decimal, for constructing decimal values from strings. The example was executed with Python 3.14.4; the linked 3.14 documentation is maintained separately.