---
metadata:
  - name: generator
    content: Diplodoc Platform v5.63.3
alternate:
  - https://ytsaurus.tech/docs/en/flow/cpp/testing.md
  - https://ytsaurus.tech/docs/ru/flow/cpp/testing.md
  - href: https://ytsaurus.tech/docs/en/flow/cpp/testing.md
    type: text/markdown
    title: Markdown version
  - href: https://ytsaurus.tech/docs/en/llms.txt?revision=181542d8c2d9f36be925894a517db8203cafae65
    rel: describedby
---
> **Documentation Index:** Fetch the complete configuration index at https://ytsaurus.tech/docs/en/llms.txt?revision=181542d8c2d9f36be925894a517db8203cafae65

<!-- source: en/_includes/flow/cpp/testing.md -->
# Testing in YTsaurus Flow (C++)

{% note info %}

C++ [workers](https://ytsaurus.tech/docs/en/flow/concepts/glossary.md#worker) currently don't have a separate framework for unit testing computations. The main approach is to run the entire pipeline with final data sources. You can find a test example in [examples/cpp/wait_click_join](https://github.com/ytsaurus/ytsaurus/tree/main/yt/yt/flow/examples/cpp/wait_click_join). If you still need to write unit tests, you can move the business logic out of the computations into separate classes and write unit tests for those classes.

{% endnote %}

<!-- source: en/_includes/flow/testing-integration-body.md -->
Use these principles when writing integration tests for pipelines:

* Test end-to-end scenarios. That is:
  * Write input/source data to the local YT.
  * In the pipeline spec, mark the sources as `finite=%true`.
  * For a [Key Visitor stream](https://ytsaurus.tech/docs/en/flow/concepts/key_visitor.md) that should run during the test, set `finite=%false`.
  * Run the pipeline.
  * When the Key Visitor should finish, call the integration test base method [`self.ask_key_visitor_to_complete("<computation_id>", "<stream_id>")`](https://github.com/ytsaurus/ytsaurus/tree/main/yt/yt/flow/library/python/integration_test_base/yt_flow_base.py); it switches that stream's [dynamic `finite` parameter](https://ytsaurus.tech/docs/en/flow/concepts/key_visitor.md#dynamic-params) to `%true`.
  * Wait for the pipeline to finish.
  * Check the output data.
* Prepare the environment with the same code you use in production:
  * Test the same pipeline binary that will run in production.
  * Generate pipeline specs with the same code that generates them for production.
  * If input/output data require non-trivial serialization/parsing, implement this logic with code shared with production.
* Use the [shared test framework](https://github.com/ytsaurus/ytsaurus/tree/main/yt/yt/flow/library/python/integration_test_base) (a `README.md` is available).
* Implement a failover test.
  * Many errors surface when workers fail and other workers take over their tasks. So, run a test with `problems=True` and more than one worker.
* Write stable tests.
  * Remember that in CI, any part of a test might run unexpectedly long.
    * The ideal runtime for a single test is 20 seconds for any build type.
    * For sanitizer builds, reduce the amount of input data.
    * Set all local timeouts with a multiple safety margin.
  * The test execution logic shouldn’t depend significantly on the current time. For example, a test shouldn’t fail if it starts before midnight and finishes after.


## Debugging tests {#debug}

### Logs {#debug-logs}

After a test finishes, you can find its logs in `test-results/py3test/testing_out_stuff` in the test directory. Key logs include:

* `run.log` — Python test logs.
* `<test_class_name>/<test_name>/Controller_<number>...` — Controller logs (`.err` is the process’s stderr; `.log` contains regular logs written via `YT_LOG_...`).
* `<test_class_name>/<test_name>/Worker_<number>...` — Worker logs.
* `<test_class_name>/<test_name>/Runner...` — Runner logs.

If something isn’t working, check errors in all these logs. The approach to reviewing them is the same as in production — see [Logs](https://ytsaurus.tech/docs/en/flow/devops/vanilla/diagnostics/logs.md).

You can also view logs before the test finishes. To do this, locate the temporary directory where the test runs. The simplest way is to run the test with the `--keep-temps` flag: `ya make --keep-temps -ttt <target>`. In this case, `ya make` won’t delete the temporary directory after the test finishes and will print a link to it in the output.

You can also use these bash aliases:

```bash
alias curtestdir="ps -f -u $USER | python3 -c \"import sys, re; drs = set(e for e in re.findall(r'[\s=](/\S*testing' + r'_out_stuff)\b', sys.stdin.read())); print('' if len(drs) == 1 else 'Select first from ' + repr(drs), file=sys.stderr); print(list(drs)[0])\""
alias cdcurtestdir='cd $(curtestdir)'

# Get the link to the local YTsaurus UI from the logs.
alias curlocalyt='cat $(curtestdir)/stderr 2>/dev/null | grep YT'
```


### Test framework {#debug-test-framework}

You can read about how to set up the environment for better test framework performance and how to influence test parameters in the framework’s [README.md](https://github.com/ytsaurus/ytsaurus/tree/main/yt/yt/flow/library/python/integration_test_base).
<!-- endsource: en/_includes/flow/testing-integration-body.md -->

<!-- source: en/_includes/flow/testing-test-param-body.md -->
You configure the behavior of integration tests using `--test-param NAME=VAL`:

#|
|| **Parameter** | **Default** | **Values** | **Action** ||
|| `RUNNER_LOG_LEVEL` | | `Error`, `Info`, `Debug`, … | Sets the logging level for the process that runs the pipeline (runner). ||
|| `PAUSE_BEFORE_FLOW_PROCESS_FEDERATION_TEARDOWN` | `0` | `0`, `1` | Pauses the test before stopping Flow processes. Combined with `--test-disable-timeout`, this lets you keep the local YTsaurus and the Flow process federation running for a long time so you can study them at your own pace via the UI. ||
|| `EXTERNAL_YT_CONFIG` | (not set) | YSON — see below | Runs the pipeline on real external YTsaurus clusters instead of the local recipe. ||
|#

Examples:

```bash
ya make -A --test-param RUNNER_LOG_LEVEL=Debug
ya make -A --test-disable-timeout --test-param PAUSE_BEFORE_FLOW_PROCESS_FEDERATION_TEARDOWN=1
```

### `EXTERNAL_YT_CONFIG` {#external-yt-config}

The local YTsaurus still starts via the recipe, but the test ignores it.

Required fields that are common for all clusters:

- `path` — base directory.
- `tablet_cell_bundle` — bundle for the dynamic tables that are created.
- `proxy_role` — RPC proxy role.

Optional: `primary_medium` (default `"default"`).

The `clusters` list — the first element is primary. Record fields: `cluster_name` (required), `proxy_url` (defaults to `cluster_name`).

Authorization: when using an external YTsaurus, `YT_TOKEN`/`YT_USER` from the local recipe are cleared; yt-wrapper picks up the token from `~/.yt/token`.

Isolation: `work_yt_path = path/<local-username>/<test_name>`; the `path/<username>` directory is deleted and recreated once per class in `setup_class`.

Example:

```bash
ya make -A --test-param 'EXTERNAL_YT_CONFIG={path="//tmp/yt_flow";tablet_cell_bundle=default;proxy_role=default;clusters=[{cluster_name=<cluster-name>};];}'
```
<!-- endsource: en/_includes/flow/testing-test-param-body.md -->

## See also

- [Basic release rules](https://ytsaurus.tech/docs/en/flow/devops/vanilla/releases.md#release-and-configure-basic-rules)
- [Testing (Java)](https://ytsaurus.tech/docs/en/flow/java/testing.md)
- [Testing (Python)](https://ytsaurus.tech/docs/en/flow/python/testing.md)
- [Quick start (C++)](https://ytsaurus.tech/docs/en/flow/cpp/getting-started.md)
- [Quick start (Java)](https://ytsaurus.tech/docs/en/flow/java/getting-started.md)
- [Quick start (Python)](https://ytsaurus.tech/docs/en/flow/python/getting-started.md)
<!-- endsource: en/_includes/flow/cpp/testing.md -->
