Skip to content

Commit cce385e

Browse files
committed
Add code explanation to README
1 parent f57f135 commit cce385e

File tree

2 files changed

+97
-2
lines changed

2 files changed

+97
-2
lines changed

README.md

+94-1
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,107 @@
11
# Example Custom NETMIKO CLI test
22

3+
This repository demonstrates how to create a custom test for NUTS (Network Unit Testing System). After installing this Python package, you can run the following example test:
4+
5+
```yaml
6+
- test_class: TestNetmikoCLI
7+
test_module: example_custom_netmiko_cli_test.netmiko_cli
8+
test_execution:
9+
command_string: show call-home
10+
use_timing: False # Determines whether to use send_command_timing (True) or send_command (False) for command execution
11+
# test_execution parameters are passed to the send_command or send_command_timing function
12+
# See Netmiko documentation for details: https://ktbyers.github.io/netmiko/docs/netmiko/index.html#netmiko.BaseConnection.send_command
13+
test_data:
14+
- host: switch01
15+
contains: "call home feature : disable"
16+
not_contains: "enable"
17+
```
318
419
## Setup Project
520
6-
Example using `uv`:
21+
This project uses [UV](https://docs.astral.sh/uv/), a fast Python package manager, though you may also use (Poetry)[https://python-poetry.org/] or any other package manager.
22+
23+
To set up with `uv`, run:
724
```bash
825
uv init example-custom-netmiko-cli-test --lib --package -p python3.10
926
uv add --dev ruff
1027
uv add --dev mypy
1128
uv add nuts
1229
```
1330

31+
## Test Implementation
32+
33+
The NUTS test class is implemented in `src/example_custom_netmiko_cli_test/netmiko_cli.py`. For detailed instructions on writing custom tests, see the [How To Write Your Own Test](https://nuts.readthedocs.io/en/latest/dev/writetests.html) documentation.
34+
35+
A NUTS test requires three classes:
36+
37+
1. **Context Class**: The `CLIContext` class provides all necessary information for test execution. `CLIContext` inherits from `NornirNutsContext`, which handles the Nornir task execution.
38+
39+
2. **Extractor Class:** The `CLIExtractor` class is responsible for extracting and transforming the task results. When using Nornir, all task results are returned as a `AggregatedResult` object. The extractor processes these results for each host, generating a `NutsResult` object for each, which is then passed to the test class.
40+
41+
3. **Test Class**: `TestNetmikoCLI` is the actual test class, where multiple test functions can be defined for different assertions.
42+
43+
44+
### CLIContext
45+
46+
The CLIContext class overrides two methods:
47+
48+
- **nuts_task**: Defines the Nornir task to execute (in this case, `netmiko_send_command`). By default, all `test_execution` parameters are passed as arguments to the task. This behavior can be customized by overriding the `nuts_arguments` method.
49+
50+
```python
51+
def nuts_task(self) -> Callable[..., Result]:
52+
return netmiko_send_command
53+
```
54+
55+
- **nuts_extractor**: Specifies the CLIExtractor object to use for processing results.
56+
57+
```python
58+
def nuts_extractor(self) -> CLIExtractor:
59+
return CLIExtractor(self)
60+
```
61+
62+
To ensure the correct context class is used, set the variable CONTEXT in your file:
1463

64+
```python
65+
CONTEXT = CLIContext
66+
```
67+
68+
NUTS will automatically discover and use this context.
69+
70+
71+
### CLIExtractor
72+
73+
The `CLIExtractor` prepares the data before passing it to the test class as a `NutsResult` object. By inheriting from `AbstractHostResultExtractor`, it maps the Nornir `AggregatedResult` to each host. The `single_transform` method, called for each host, transforms the `MultiResult` into a `NutsResult`. The `_simple_extract(single_result)` method extracts the first result from the `MultiResult` — standard behavior when there are no Nornir subtasks.
74+
75+
```python
76+
class CLIExtractor(AbstractHostResultExtractor):
77+
def single_transform(self, single_result: MultiResult) -> Dict[str, Dict[str, Any]]:
78+
cli_result = self._simple_extract(single_result)
79+
return cli_result
80+
```
81+
82+
### TestNetmikoCLI
83+
84+
The [custom pytest marker](https://docs.pytest.org/en/stable/example/markers.html) is used to pass specific data from the `test_data` section in the YAML test definition as a pytest fixture. In this example, the `contains` value is passed to the test function, allowing it to validate whether this value appears in the command output. For instance, based on the example YAML, the string `"call home feature : disable"` is passed for `switch01`, and the test checks if it is part of the result.
85+
86+
The test context is accessible via the `nuts_ctx` fixture, which allows additional functionality, such as enhancing error messages if assertions fail. In this example, the code retrieves the command string (`command_string`) for better error reporting. If a value specified in pytest nuts marker is not defined in the test_data section, the test is automatically skipped.
87+
88+
```python
89+
class TestNetmikoCLI:
90+
@pytest.mark.nuts("contains")
91+
def test_contains_in_result(
92+
self, nuts_ctx, single_result: NutsResult, contains: Any
93+
) -> None:
94+
cmd = nuts_ctx.nuts_parameters.get("test_execution", {}).get(
95+
"command_string", None
96+
)
97+
result = single_result.result
98+
assert contains in result, f"'{contains}' NOT found in '{cmd}' output"
99+
```
100+
101+
You can also pass multiple values to a test function, as shown in this example from the [documentation](https://nuts.readthedocs.io/en/latest/dev/writetests.html#writing-the-test-itself):
102+
103+
```python
104+
@pytest.mark.nuts("name, role")
105+
def test_role(self, single_result: NutsResult, name: str, role: str) -> None:
106+
assert single_result.result[name]["role"] == role
107+
```

src/example_custom_netmiko_cli_test/netmiko_cli.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@
1717
test_module: example_custom_netmiko_cli_test.netmiko_cli
1818
test_execution:
1919
command_string: show call-home
20-
use_timing: False
20+
use_timing: False # Determines whether to use send_command_timing (True) or send_command (False) for command execution
21+
# test_execution parameters are passed to the send_command or send_command_timing function
22+
# See Netmiko documentation for details: https://ktbyers.github.io/netmiko/docs/netmiko/index.html#netmiko.BaseConnection.send_command
2123
test_data:
2224
- host: switch01
2325
contains: "call home feature : disable"

0 commit comments

Comments
 (0)