Understanding Python Iterables and Iterators and Their Use in Automated Testing
This article explains the difference between iterables and iterators in Python, demonstrates how to implement them with custom classes, shows basic usage with built‑in collections, and provides multiple automation‑testing examples using generators and iterator patterns to handle dynamic test data and pagination.
Introduction In Python, an iterable is an object that implements the __iter__() method, while an iterator implements the __next__() method. Understanding their distinction helps write clearer code.
Iterable An iterable returns an iterator via __iter__() . Example:
class MyIterable:
def __init__(self, data):
self.data = data
def __iter__(self):
return iter(self.data)
# Create an iterable instance
my_iterable = MyIterable([1, 2, 3])
for item in my_iterable:
print(item) # 输出: 1 2 3Iterator An iterator provides the __next__() method to retrieve the next element and raises StopIteration when exhausted. Example:
class MyIterator:
def __init__(self, data):
self.data = data
self.position = 0
def __next__(self):
if self.position >= len(self.data):
raise StopIteration
item = self.data[self.position]
self.position += 1
return item
# Create an iterator instance
my_iterator = MyIterator([1, 2, 3])
print(next(my_iterator)) # 输出: 1
print(next(my_iterator)) # 输出: 2
print(next(my_iterator)) # 输出: 3
# print(next(my_iterator)) # 抛出 StopIterationRelationship An iterable’s __iter__() returns an iterator, which implements the actual iteration logic via __next__() .
Basic Combined Example A custom iterable that returns a custom iterator:
class MyIterable:
def __init__(self, data):
self.data = data
def __iter__(self):
return MyIterator(self.data)
class MyIterator:
def __init__(self, data):
self.data = data
self.position = 0
def __next__(self):
if self.position >= len(self.data):
raise StopIteration
item = self.data[self.position]
self.position += 1
return item
my_iterable = MyIterable([1, 2, 3])
for item in my_iterable:
print(item) # 输出: 1 2 3Built‑in Iterable Types Lists, tuples, sets, and dictionaries are all iterables. Examples:
for item in [1, 2, 3]:
print(item) # 输出: 1 2 3
d = {'a': 1, 'b': 2}
for key in d:
print(key) # 输出: a b
for value in d.values():
print(value) # 输出: 1 2
for item in d.items():
print(item) # 输出: ('a', 1) ('b', 2)Automation‑Related Examples
Example 1: Generator for Dynamic Test Data
import requests
import yaml
def generate_test_cases(filename):
with open(filename, 'r') as stream:
test_cases = yaml.safe_load(stream)['tests']
for test_case in test_cases:
yield test_case
def test_api_with_generator():
generator = generate_test_cases('data/api_tests.yaml')
for test_case in generator:
url = test_case['url']
method = test_case['method']
headers = test_case.get('headers', {})
body = test_case.get('body', {})
expected_status = test_case['expected_status']
response = requests.request(method, url, headers=headers, json=body)
assert response.status_code == expected_status, f"Expected status code {expected_status}, got {response.status_code}"
test_api_with_generator()Example 2: Iterator for Paginated Data
import requests
class PageIterator:
def __init__(self, base_url, params):
self.base_url = base_url
self.params = params
self.page = 1
def __iter__(self):
return self
def __next__(self):
response = requests.get(self.base_url, params={**self.params, "page": self.page})
if response.status_code != 200:
raise RuntimeError(f"Request failed with status code {response.status_code}")
data = response.json()
if not data['results']:
raise StopIteration
self.page += 1
return data['results']
def test_pagination():
base_url = "https://api.example.com/data"
params = {"limit": 10}
page_iterator = PageIterator(base_url, params)
for page_data in page_iterator:
print(page_data)
test_pagination()Example 3: Iterator for Multi‑Environment Testing
import requests
import yaml
class EnvironmentIterator:
def __init__(self, filename):
with open(filename, 'r') as stream:
self.environments = yaml.safe_load(stream)['environments']
self.index = 0
def __iter__(self):
return self
def __next__(self):
if self.index < len(self.environments):
env = self.environments[self.index]
self.index += 1
return env
else:
raise StopIteration
def test_environments():
environment_iterator = EnvironmentIterator('data/environments.yaml')
for env in environment_iterator:
base_url = env['base_url']
response = requests.get(f"{base_url}/health")
assert response.status_code == 200, f"Health check failed for {base_url}"
test_environments()Example 4: Generator Expression for Simplified Test Logic
import requests
import yaml
def test_api_with_generator_expression():
test_cases = (
{'url': "https://api.example.com/user/123", 'method': "GET", 'headers': {"Authorization": "Bearer token1"}, 'expected_status': 200},
{'url': "https://api.example.com/user", 'method': "POST", 'headers': {"Authorization": "Bearer token2"}, 'body': {"name": "Jane Doe", "email": "[email protected]"}, 'expected_status': 201}
)
for test_case in test_cases:
response = requests.request(test_case['method'], test_case['url'], headers=test_case.get('headers'), json=test_case.get('body'))
assert response.status_code == test_case['expected_status'], f"Expected status code {test_case['expected_status']}, got {response.status_code}"
test_api_with_generator_expression()Example 5: Iterator for Dynamic Parameter Combinations
import requests
import itertools
class ParameterCombinations:
def __init__(self, urls, methods, headers, bodies, expected_statuses):
self.urls = urls
self.methods = methods
self.headers = headers
self.bodies = bodies
self.expected_statuses = expected_statuses
self.combinations = itertools.product(self.urls, self.methods, self.headers, self.bodies, self.expected_statuses)
self.counter = 0
def __iter__(self):
return self
def __next__(self):
if self.counter < len(self.combinations):
url, method, header, body, expected_status = self.combinations[self.counter]
test_case = {
'url': url,
'method': method,
'headers': header,
'body': body,
'expected_status': expected_status
}
self.counter += 1
return test_case
else:
raise StopIteration
def test_parameter_combinations():
urls = ["https://api.example.com/user/123", "https://api.example.com/user/456"]
methods = ["GET", "POST"]
headers = [{"Authorization": "Bearer token1"}, {"Authorization": "Bearer token2"}]
bodies = [{}, {"name": "Jane Doe", "email": "[email protected]"}]
expected_statuses = [200, 201]
combinations_iterator = ParameterCombinations(urls, methods, headers, bodies, expected_statuses)
for test_case in combinations_iterator:
response = requests.request(test_case['method'], test_case['url'], headers=test_case['headers'], json=test_case.get('body'))
assert response.status_code == test_case['expected_status'], f"Expected status code {test_case['expected_status']}, got {response.status_code}"
test_parameter_combinations()Conclusion The examples demonstrate how Python’s iteration protocol—iterables, iterators, generators, and generator expressions—can be leveraged in API automation testing to manage complex data flows, reduce memory usage, and simplify test logic, ultimately improving code readability, maintainability, and flexibility.
Test Development Learning Exchange
Test Development Learning Exchange
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.