Unit testing DAGs in Apache Airflow - Time & Space Complexity
When we test Airflow DAGs, we want to know how the time to run tests changes as the DAG grows.
We ask: How does adding more tasks affect the test time?
Analyze the time complexity of the following unit test for an Airflow DAG.
from airflow.models import DagBag
import unittest
class TestMyDAG(unittest.TestCase):
def test_dag_loads(self):
dagbag = DagBag()
dag = dagbag.get_dag('my_dag')
self.assertIsNotNone(dag)
self.assertFalse(dagbag.import_errors)
This test loads the DAGs and checks if 'my_dag' loads without errors.
Look for repeated steps in the test process.
- Primary operation: Loading all DAG files in the DagBag.
- How many times: Once per test run, but loading involves reading each DAG file.
As the number of DAG files increases, loading takes longer.
| Input Size (number of DAG files) | Approx. Operations (loading DAGs) |
|---|---|
| 10 | 10 file reads and parses |
| 100 | 100 file reads and parses |
| 1000 | 1000 file reads and parses |
Pattern observation: The time grows roughly in direct proportion to the number of DAG files.
Time Complexity: O(n)
This means test time grows linearly as the number of DAG files increases.
[X] Wrong: "Unit testing a DAG always takes constant time regardless of DAG size."
[OK] Correct: Loading the DAGs requires reading each file, so more DAGs mean more work and longer test time.
Understanding how test time grows helps you write efficient tests and manage large Airflow projects confidently.
"What if we only loaded a single DAG file instead of all DAGs? How would the time complexity change?"
Practice
Solution
Step 1: Understand unit testing purpose
Unit testing is done to find bugs or errors early in code before actual execution.Step 2: Apply this to Airflow DAGs
Unit testing DAGs helps catch errors in DAG definitions before running workflows in Airflow.Final Answer:
To catch errors early before running workflows -> Option AQuick Check:
Unit testing = catch errors early [OK]
- Confusing unit testing with deployment
- Thinking unit tests schedule DAGs
- Mixing monitoring with testing
Solution
Step 1: Recall DagBag constructor usage
DagBag takes a keyword argument dag_folder to specify DAGs folder path.Step 2: Check each option
Only dagbag = DagBag(dag_folder='/path/to/dags') uses correct syntax. Others use invalid methods (A,D) or incorrect keyword argument like folder (B).Final Answer:
dagbag = DagBag(dag_folder='/path/to/dags') -> Option BQuick Check:
DagBag(dag_folder=...) correct syntax [OK]
- Using incorrect keyword argument (e.g., folder)
- Using non-existent methods like load() or get()
- Omitting dag_folder keyword
dagbag = DagBag(dag_folder='dags')
dag = dagbag.get_dag('example_dag')
print(len(dag.tasks))Solution
Step 1: Load DAG using DagBag
DagBag loads DAGs from 'dags' folder; get_dag('example_dag') returns the DAG object if found.Step 2: Count tasks in the DAG
len(dag.tasks) returns the number of tasks in the DAG, which is 3 as given.Final Answer:
3 -> Option CQuick Check:
len(dag.tasks) = 3 [OK]
- Assuming get_dag returns None if DAG exists
- Confusing tasks with task instances
- Expecting error if DAG is present
dagbag = DagBag(dag_folder='dags')
dag = dagbag.get_dag('missing_dag')
print(len(dag.tasks))Solution
Step 1: Analyze get_dag return value
get_dag returns None if the DAG name is not found in the loaded DAGs.Step 2: Understand AttributeError cause
Trying to access dag.tasks when dag is None causes AttributeError.Final Answer:
The DAG 'missing_dag' does not exist in the folder -> Option AQuick Check:
get_dag returns None if DAG missing [OK]
- Assuming get_dag always returns a DAG
- Thinking len() is invalid on tasks list
- Believing DagBag can't load DAGs from folder
Solution
Step 1: Check import errors correctly
dagbag.import_errors is a dict; empty dict {} means no import errors.Step 2: Verify task count assertion
len(dag.tasks) == 5 checks that the DAG has exactly 5 tasks.Final Answer:
dagbag = DagBag(dag_folder='dags') assert dagbag.import_errors == {} dag = dagbag.get_dag('data_pipeline') assert len(dag.tasks) == 5 -> Option DQuick Check:
import_errors == {} and task count 5 [OK]
- Checking import_errors against None instead of {}
- Asserting wrong task count
- Confusing import_errors presence logic
Ready to prove it?
Pass the test and this pattern moves unknown
