CSV Source¶
CSVSource reads a CSV file and replays each row as a data stream message inside your automated test. Your app receives the data exactly as it would from a live data source, but the whole replay happens through a virtual clock with no real-time waiting.
Use this when you have recorded data from your process and want to write repeatable assertions against it.
Read Test Harness & Setup first if you have not already.
Your CSV file¶
Structure your CSV with these columns:
timestamp— event time for each rowasset_name— which asset the row belongs to- One column per input data stream defined in your
app.yaml
timestamp,asset_name,temperature
2023-10-27T10:00:01,doc_demo_plunger_01,50
2023-10-27T10:00:02,doc_demo_plunger_01,51
2023-10-27T10:00:03,doc_demo_plunger_01,52
2023-10-27T10:00:04,doc_demo_plunger_01,55
2023-10-27T10:00:05,doc_demo_plunger_01,60
2023-10-27T10:00:06,doc_demo_plunger_02,56
2023-10-27T10:00:07,doc_demo_plunger_02,58
2023-10-27T10:00:08,doc_demo_plunger_02,60
Basic usage¶
Pass the source to the harness before running the test:
Constructor parameters¶
| Parameter | Default | Description |
|---|---|---|
path |
Required | Path to your CSV file. |
playback |
False |
When True, waits between rows based on the actual timestamp difference in the CSV, replaying at real-time speed. |
ignore_timestamps |
False |
When True, ignores CSV timestamps and uses virtual clock time instead. |
now_offset |
False |
When True, shifts all timestamps so the first row starts at the current time. |
asset_column |
"asset_name" |
Name of the column that contains asset names. |
timestamp_column |
"timestamp" |
Name of the column that contains timestamps. |
Example using constructor parameters:
source = CSVSource(
path="data/test-data.csv",
ignore_timestamps=True, # use virtual clock time instead of CSV timestamps
asset_column="device_id", # if your CSV uses a different column name for assets
)
Fluent methods¶
Chain these after CSVSource(...) to configure the source:
| Method | Description |
|---|---|
.with_columns("col1", "col2") |
Select specific columns to publish. All other columns are ignored. |
.with_asset("asset_name") |
Fallback asset name when the CSV has no asset_name column. |
.with_timing(timedelta(seconds=1)) |
Override the interval between row messages. |
.with_column_mapping({"csv_col": "stream_name"}) |
Map CSV column names to the data stream names in your app. |
Example:
from datetime import timedelta
source = (
CSVSource("data/test-data.csv")
.with_columns("temperature") # publish only this column
.with_column_mapping({"temp_c": "temperature"}) # rename if column name differs
.with_asset("doc_demo_plunger_01") # fallback if no asset_name column
)
Full test example¶
import pytest
from main import app
from kelvin.testing import KelvinAppTest, ManifestBuilder
from kelvin.testing.sources import CSVSource
from kelvin.message import RecommendationMsg
manifest = (
ManifestBuilder()
.add_input("temperature")
.add_asset("doc_demo_plunger_01", parameters={"limit": 55})
.add_asset("doc_demo_plunger_02", parameters={"limit": 55})
.build()
)
source = (
CSVSource("data/test-data.csv")
.with_columns("temperature")
)
@pytest.mark.asyncio
async def test_alerts_from_csv_data():
harness = KelvinAppTest(app, manifest=manifest).add_source(source)
async with harness:
await harness.run_until_idle(timeout=15.0)
outputs = harness.outputs
recommendations = [o for o in outputs if isinstance(o, RecommendationMsg)]
assert len(recommendations) >= 1
Run the tests¶
pytest exits with code 0 if all assertions pass, or non-zero if any fail.
For the manual CLI equivalent, see Manual Testing with CSV File.