Skip to content

Commit cd88e9f

Browse files
committed
Update test_workflows.py
1 parent 14c6b7c commit cd88e9f

1 file changed

Lines changed: 95 additions & 10 deletions

File tree

examples/test_workflows.py

Lines changed: 95 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,36 @@
1+
"""
2+
Workflow Unit Testing Example
3+
==============================
4+
5+
This module demonstrates how to write unit tests for Conductor workflows and workers.
6+
7+
Key Concepts:
8+
-------------
9+
1. **Worker Testing**: Test worker functions independently as regular Python functions
10+
2. **Workflow Testing**: Test complete workflows end-to-end with mocked task outputs
11+
3. **Mock Outputs**: Simulate task execution results without running actual workers
12+
4. **Retry Simulation**: Test retry logic by providing multiple outputs (failed then succeeded)
13+
5. **Decision Testing**: Verify switch/decision logic with different input scenarios
14+
15+
Test Types:
16+
-----------
17+
- **Unit Test (test_greetings_worker)**: Tests a single worker function in isolation
18+
- **Integration Test (test_workflow_execution)**: Tests complete workflow with mocked dependencies
19+
20+
Running Tests:
21+
--------------
22+
python3 -m unittest discover --verbose --start-directory=./
23+
python3 -m unittest examples.test_workflows.WorkflowUnitTest
24+
25+
Use Cases:
26+
----------
27+
- Validate workflow logic before deployment
28+
- Test error handling and retry behavior
29+
- Verify decision/switch conditions
30+
- CI/CD pipeline integration
31+
- Regression testing for workflow changes
32+
"""
33+
134
import unittest
235

336
from conductor.client.configuration.configuration import Configuration
@@ -11,11 +44,13 @@
1144

1245
class WorkflowUnitTest(unittest.TestCase):
1346
"""
14-
This is an example of how to write a UNIT test for the workflow
15-
to run:
16-
17-
python3 -m unittest discover --verbose --start-directory=./
47+
Unit tests for Conductor workflows and workers.
1848
49+
This test suite demonstrates:
50+
- Testing individual worker functions
51+
- Testing complete workflow execution with mocked task outputs
52+
- Simulating task failures and retries
53+
- Validating workflow decision logic
1954
"""
2055
@classmethod
2156
def setUpClass(cls) -> None:
@@ -26,33 +61,75 @@ def setUpClass(cls) -> None:
2661

2762
def test_greetings_worker(self):
2863
"""
29-
Tests for the workers
30-
Conductor workers are regular python functions and can be unit or integrated tested just like any other function
64+
Unit test for a worker function.
65+
66+
Demonstrates:
67+
- Worker functions are regular Python functions that can be tested directly
68+
- No need to start worker processes or connect to Conductor server
69+
- Fast, isolated testing of business logic
70+
- Can use standard Python testing tools (unittest, pytest, etc.)
71+
72+
This approach is ideal for:
73+
- Testing worker logic in isolation
74+
- Running tests in CI/CD pipelines
75+
- Test-driven development (TDD)
76+
- Quick feedback during development
3177
"""
3278
name = 'test'
3379
result = greet(name=name)
3480
self.assertEqual(f'Hello {name}', result)
3581

3682
def test_workflow_execution(self):
3783
"""
38-
Test a complete workflow end to end with mock outputs for the task executions
84+
Integration test for a complete workflow with mocked task outputs.
85+
86+
Demonstrates:
87+
- Testing workflow logic without running actual workers
88+
- Mocking task outputs to simulate different scenarios
89+
- Testing retry behavior (task failure followed by success)
90+
- Testing decision/switch logic with different inputs
91+
- Validating workflow execution paths
92+
93+
Key Benefits:
94+
- Fast execution (no actual task execution)
95+
- Deterministic results (mocked outputs)
96+
- No external dependencies (no worker processes)
97+
- Test error scenarios safely
98+
- Validate workflow structure and logic
99+
100+
Workflow Structure:
101+
-------------------
102+
1. HTTP task (always succeeds)
103+
2. task1 (fails first, succeeds on retry with city='NYC')
104+
3. Switch decision based on task1.output('city')
105+
4. If city='NYC': execute task2
106+
5. Otherwise: execute task3
107+
108+
Expected Flow:
109+
--------------
110+
HTTP → task1 (FAILED) → task1 (RETRY, COMPLETED) → switch → task2
39111
"""
112+
# Create workflow with tasks
40113
wf = ConductorWorkflow(name='unit_testing_example', version=1, executor=self.workflow_executor)
41114
task1 = SimpleTask(task_def_name='hello', task_reference_name='hello_ref_1')
42115
task2 = SimpleTask(task_def_name='hello', task_reference_name='hello_ref_2')
43116
task3 = SimpleTask(task_def_name='hello', task_reference_name='hello_ref_3')
44117

118+
# Switch decision: if city='NYC' → task2, else → task3
45119
decision = SwitchTask(task_ref_name='switch_ref', case_expression=task1.output('city'))
46120
decision.switch_case('NYC', task2)
47121
decision.default_case(task3)
48122

123+
# HTTP task to simulate external API call
49124
http = HttpTask(task_ref_name='http', http_input={'uri': 'https://orkes-api-tester.orkesconductor.com/api'})
50125
wf >> http
51126
wf >> task1 >> decision
52127

128+
# Mock outputs for each task
53129
task_ref_to_mock_output = {}
54130

55-
# task1 has two attempts, first one failed and second succeeded
131+
# task1 has two attempts: first fails, second succeeds
132+
# This tests retry behavior
56133
task_ref_to_mock_output[task1.task_reference_name] = [{
57134
'status': 'FAILED',
58135
'output': {
@@ -62,11 +139,12 @@ def test_workflow_execution(self):
62139
{
63140
'status': 'COMPLETED',
64141
'output': {
65-
'city': 'NYC'
142+
'city': 'NYC' # This triggers the switch to execute task2
66143
}
67144
}
68145
]
69146

147+
# task2 succeeds (executed because city='NYC')
70148
task_ref_to_mock_output[task2.task_reference_name] = [
71149
{
72150
'status': 'COMPLETED',
@@ -76,6 +154,7 @@ def test_workflow_execution(self):
76154
}
77155
]
78156

157+
# HTTP task succeeds
79158
task_ref_to_mock_output[http.task_reference_name] = [
80159
{
81160
'status': 'COMPLETED',
@@ -85,26 +164,32 @@ def test_workflow_execution(self):
85164
}
86165
]
87166

167+
# Execute workflow test with mocked outputs
88168
test_request = WorkflowTestRequest(name=wf.name, version=wf.version,
89169
task_ref_to_mock_output=task_ref_to_mock_output,
90170
workflow_def=wf.to_workflow_def())
91171
run = self.workflow_client.test_workflow(test_request=test_request)
92172

173+
# Verify workflow completed successfully
93174
print(f'completed the test run')
94175
print(f'status: {run.status}')
95176
self.assertEqual(run.status, 'COMPLETED')
96177

178+
# Verify HTTP task executed first
97179
print(f'first task (HTTP) status: {run.tasks[0].task_type}')
98180
self.assertEqual(run.tasks[0].task_type, 'HTTP')
99181

182+
# Verify task1 failed on first attempt (retry test)
100183
print(f'{run.tasks[1].reference_task_name} status: {run.tasks[1].status} (expected to be FAILED)')
101184
self.assertEqual(run.tasks[1].status, 'FAILED')
102185

186+
# Verify task1 succeeded on retry
103187
print(f'{run.tasks[2].reference_task_name} status: {run.tasks[2].status} (expected to be COMPLETED')
104188
self.assertEqual(run.tasks[2].status, 'COMPLETED')
105189

190+
# Verify switch decision executed task2 (because city='NYC')
106191
print(f'{run.tasks[4].reference_task_name} status: {run.tasks[4].status} (expected to be COMPLETED')
107192
self.assertEqual(run.tasks[4].status, 'COMPLETED')
108193

109-
# assert that the task2 was executed
194+
# Verify the correct branch was taken (task2, not task3)
110195
self.assertEqual(run.tasks[4].reference_task_name, task2.task_reference_name)

0 commit comments

Comments
 (0)