How to Use Python to Implement Soft Chain Assertion
Test automation efficiency hinges on one critical factor: how quickly you can identify all failures in a single test run. Traditional assertions stop at the first error, forcing you to fix issues one at a time and rerun tests repeatedly.
This guide demonstrates how to use Python to implement soft chain assertion techniques that collect all failures and report them together, dramatically reducing debugging time and improving test suite efficiency.
Understanding the Critical Difference Between Hard and Soft Assertions
Python’s standard assert statement implements a fail-fast approach. When an assertion fails, Python raises an AssertionError immediately, halting execution of the current test function.
This behavior serves well for critical preconditions but becomes a significant bottleneck when validating multiple independent properties.
Consider testing an e-commerce product page. You need to verify the product title, price, availability status, rating, shipping information, and image URLs.
With hard assertions, a failed title check prevents you from discovering that the price format is also incorrect. You fix the title, rerun the test, discover the price issue, fix it, rerun again, and eventually find the shipping data problem. This iterative cycle wastes substantial development time.
For teams developing complex applications, whether working with a mobile app development company in New York or building web platforms, efficient testing becomes even more critical as application complexity increases.
Research from test automation surveys indicates that teams implementing soft assertions effectively identify issues in 30% fewer test runs. When each test execution reveals multiple failures instead of just one, your debugging workflow becomes exponentially more efficient.
Why Fail-Fast Assertions Create Development Bottlenecks
The fail-fast model creates several challenges in modern test automation:
Incomplete Diagnostic Information: You discover failures sequentially rather than comprehensively. A test suite with 50 assertions across 10 objects might require 15–20 test runs to uncover all issues if each run only reveals 2–3 failures before stopping.
Reduced Developer Productivity: According to industry data, test automation can reduce feedback response time to developers by up to 80% when implemented effectively. However, this benefit diminishes significantly when tests must run multiple times to reveal all failures.
Wasted CI/CD Resources: In continuous integration pipelines, each test run consumes computational resources and time. Multiple runs to discover all failures multiply these costs unnecessarily.
Delayed Bug Discovery: When testing complex data structures or API responses with dozens of fields, sequential failure discovery can delay bug identification by hours or even days.
The Comprehensive Soft Assertion Solution
Soft assertions fundamentally change the testing paradigm by collecting all assertion failures during test execution and reporting them together at the end. Instead of stopping at the first failure, the test continues evaluating all conditions, building a complete failure report.
This approach provides development teams with a comprehensive view of application health in a single test run. You immediately see that five out of twenty API response fields are incorrect, rather than discovering them one at a time through repeated test executions.
The implementation strategy varies across Python testing frameworks, but the underlying principle remains consistent: defer raising the AssertionError until all assertions have been evaluated.
Python Soft Assertion Libraries: A Detailed Comparison
Python lacks native soft assertion support, but the ecosystem provides several excellent libraries that fill this gap. Each offers distinct advantages depending on your testing framework and requirements.
pytest-check: The Industry Standard for pytest
The pytest-check plugin has emerged as the most widely adopted solution for pytest users. Released with ongoing maintenance through 2025, it provides both context manager and direct function call approaches.
Installation:
pip install pytest-checkKey Advantages:
- Native integration with pytest’s reporting system
- Supports both context manager and direct function syntax
- Actively maintained with regular updates through 2025
- Comprehensive failure reporting with pseudo-tracebacks
- Configurable output limits for performance optimization
Usage Example:
import pytest_check as checkdef test_user_profile_complete():
user_data = {
"username": "testuser",
"email": "wrong@example.com",
"age": 17,
"verified": False,
"role": "guest"
}
# Context manager approach
with check:
assert user_data["username"] == "expecteduser"
assert user_data["email"] == "expected@example.com"
assert user_data["age"] >= 18
assert user_data["verified"] is True
assert user_data["role"] == "admin"All five assertions execute regardless of failures, and pytest-check generates a detailed report showing each failed condition with actual vs. expected values.
Alternative Direct Function Approach:
import pytest_check as checkdef test_api_response():
response = make_api_call()
check.equal(response.status_code, 200, "Status code mismatch")
check.is_true(response.json()["success"], "Success flag not true")
check.greater(len(response.json()["data"]), 0, "Empty data array")
check.is_in("id", response.json()["data"][0], "ID field missing")assertpy: Fluent Assertions with Soft Capability
The assertpy library provides a fluent assertion API inspired by AssertJ from the Java ecosystem. It offers excellent readability through method chaining and includes robust soft assertion support.
Installation:
pip install assertpyKey Advantages:
- Highly readable fluent API
- Works with pytest, unittest, and standalone
- Extensive assertion methods for various data types
- Built-in support for extracting and validating nested data
- Clean syntax for complex validation chains
Implementation:
from assertpy import assert_that, soft_assertionsdef test_product_data():
product = {
"name": "Widget",
"price": 150.00,
"in_stock": False,
"category": "electronics",
"rating": 3.5
}
with soft_assertions():
assert_that(product["name"]).is_equal_to("Super Widget")
assert_that(product["price"]).is_less_than(100.00)
assert_that(product["in_stock"]).is_true()
assert_that(product["category"]).is_equal_to("tools")
assert_that(product["rating"]).is_greater_than_or_equal_to(4.0)Chaining for Cleaner Code:
from assertpy import assert_that, soft_assertionsdef test_list_validation():
user_ids = [101, 102, 103, 104, 105]
with soft_assertions():
assert_that(user_ids).is_not_empty()\
.is_length(5)\
.contains(101, 105)\
.does_not_contain(999)smart-assertions: Explicit Verification Pattern
The smart-assertions library implements a verification pattern where assertions are checked immediately but results are collected until explicitly verified.
Installation:
pip install smart-assertionsUsage:
from smart_assertions import soft_assert, verify_expectationsdef test_temperature_readings():
readings = get_sensor_data()
soft_assert(readings["temp"] > 0, "Temperature below zero")
soft_assert(readings["humidity"] < 100, "Humidity exceeds maximum")
soft_assert(readings["pressure"] > 980, "Pressure too low")
soft_assert(readings["altitude"] >= 0, "Invalid altitude")
verify_expectations() # Raises AssertionError with all failuressoft-assert: Context-Based Verification
This library provides a clean context manager syntax specifically designed for pytest integration.
Get Sherry walker’s stories in your inbox
Join Medium for free to get updates from this writer.
Installation:
pip install soft-assertImplementation:
from soft_assert import check, verifydef test_database_records():
with verify():
for record in get_records():
check(record.id is not None, f"Missing ID in record")
check(record.created_at is not None, f"Missing timestamp")
check(len(record.data) > 0, f"Empty data field")Step-by-Step Implementation with pytest-check
For most Python developers using pytest, pytest-check represents the optimal choice due to its active maintenance, comprehensive features, and seamless integration. Here’s a complete implementation guide.
Installation and Setup
Install pytest-check in your project environment:
pip install pytest-checkNo additional configuration is required. The plugin automatically integrates with pytest once installed.
Basic Implementation Pattern
import pytest_check as checkdef test_user_registration_form():
"""Validate all fields in user registration response"""
registration_data = submit_registration_form()
with check:
assert registration_data["status"] == "success"
assert "user_id" in registration_data
assert registration_data["email_sent"] is True
assert registration_data["verification_token"] is not None
assert len(registration_data["username"]) >= 3Advanced Usage with API Testing
import pytest_check as check
import requestsdef test_api_endpoint_comprehensive():
"""Test multiple aspects of API response"""
response = requests.get("https://api.example.com/users/123")
# Hard assertion for critical precondition
assert response.status_code == 200, "API request failed"
# Soft assertions for response validation
data = response.json()
with check:
assert "id" in data, "Missing user ID"
assert "name" in data, "Missing user name"
assert "email" in data, "Missing email"
assert data["active"] is True, "User not active"
assert len(data["permissions"]) > 0, "No permissions"
# Additional validation using direct functions
check.is_instance(data["id"], int)
check.is_not_none(data["created_at"])
check.greater_equal(data["login_count"], 0)Loop-Based Validation
import pytest_check as checkdef test_multiple_user_records():
"""Validate collection of user records"""
users = fetch_user_batch()
for user in users:
with check:
assert user.email is not None, f"User {user.id} missing email"
assert "@" in user.email, f"Invalid email for {user.id}"
assert user.age >= 18, f"User {user.id} under age limit"
assert user.verified is True, f"User {user.id} not verified"Creating a Custom Soft Assertion Context Manager
For teams requiring specialized behavior or working without pytest, implementing a custom soft assertion context manager provides complete control.
class SoftAssertions:
def __init__(self):
self.failures = []
def __enter__(self):
return self
def __exit__(self, exc_type, exc_val, exc_tb):
if self.failures:
failure_messages = "\n".join(
f"{i+1}. {msg}" for i, msg in enumerate(self.failures)
)
raise AssertionError(
f"Soft assertion failures ({len(self.failures)}):\n{failure_messages}"
)
return True
def check(self, condition, message="Assertion failed"):
if not condition:
import traceback
tb = traceback.extract_stack()[-2]
location = f"{tb.filename}:{tb.lineno}"
self.failures.append(f"{message} (at {location})")# Usage
def test_custom_soft_assertions():
with SoftAssertions() as soft:
soft.check(2 + 2 == 4, "Basic math failed")
soft.check("hello".upper() == "HELLO", "String conversion failed")
soft.check([1, 2, 3][-1] == 3, "List indexing failed")Real-World Implementation Patterns and Best Practices
Strategic Combination of Hard and Soft Assertions
The most effective test suites strategically combine both assertion types. Use hard assertions for critical preconditions and soft assertions for validating outcomes.
import pytest_check as check
import requestsdef test_user_dashboard():
# Hard assertion: must be logged in to proceed
login_response = authenticate_user()
assert login_response.status_code == 200, "Login failed"
# Hard assertion: must receive valid token
token = login_response.json()["token"]
assert token is not None, "No authentication token"
# Now soft-assert all dashboard elements
dashboard = fetch_dashboard(token)
with check:
assert dashboard["user_name"] == "Expected Name"
assert dashboard["account_balance"] > 0
assert len(dashboard["recent_transactions"]) >= 5
assert dashboard["notification_count"] >= 0
assert "profile_image_url" in dashboardValidating Complex Data Structures
Soft assertions excel when validating objects with multiple properties:
import pytest_check as checkdef test_order_object():
order = create_test_order()
with check:
# Order header validation
assert order.order_id is not None
assert order.customer_id == 12345
assert order.total_amount == 299.99
assert order.status == "pending"
# Items validation
assert len(order.items) == 3
assert sum(item.quantity for item in order.items) == 5
# Shipping validation
assert order.shipping_address.zip_code == "12345"
assert order.shipping_method == "express"
# Payment validation
assert order.payment_method == "credit_card"
assert order.payment_status == "authorized"Form Validation Testing
import pytest_check as check
from selenium import webdriverdef test_registration_form_validation():
driver = webdriver.Chrome()
driver.get("https://example.com/register")
# Fill form with invalid data
fill_registration_form(driver, invalid_data=True)
submit_form(driver)
# Verify all validation messages appear
with check:
assert is_visible(driver, "#username-error")
assert is_visible(driver, "#email-error")
assert is_visible(driver, "#password-error")
assert is_visible(driver, "#terms-error")
# Verify error message content
assert get_text(driver, "#username-error") == "Username too short"
assert get_text(driver, "#email-error") == "Invalid email format"
assert get_text(driver, "#password-error") == "Password too weak"Performance Optimization and Configuration
pytest-check Configuration Options
pytest-check provides several command-line options for controlling output and performance:
# Limit pseudo-traceback generation (improves performance)
pytest --check-max-tb=1# Limit reported failures per test
pytest --check-max-report=10# Stop test after specified number of failures
pytest --check-max-fail=20Add these to pytest.ini for permanent configuration:
[pytest]
addopts = --check-max-tb=1 --check-max-report=15Managing Large Test Suites
When testing hundreds of properties, structure assertions logically:
import pytest_check as checkdef test_large_api_response():
response = fetch_large_dataset()
# Group related checks
with check: # Basic structure
assert "data" in response
assert "metadata" in response
assert "pagination" in response
with check: # Data validation
assert len(response["data"]) > 0
assert all("id" in item for item in response["data"])
with check: # Metadata validation
assert response["metadata"]["total"] > 0
assert response["metadata"]["version"] == "2.0"Common Pitfalls and How to Avoid Them
Overusing Soft Assertions
Not every test should use soft assertions. Avoid them when:
- Testing sequential operations where each step depends on the previous
- Validating critical security checks
- Testing database transactions where rollback is needed on first failure
- Setup and teardown operations
Bad Example:
def test_sequential_operations():
with check: # DON'T DO THIS
assert create_user() is not None
assert assign_permissions() is True
assert send_welcome_email() is TrueGood Example:
def test_sequential_operations():
# Hard assertions for sequential dependencies
user = create_user()
assert user is not None, "User creation failed"
permissions = assign_permissions(user)
assert permissions is True, "Permission assignment failed"
# Soft assertions for independent validations
with check:
assert user.email_verified is False
assert user.role == "standard"
assert len(user.permissions) == 5Inadequate Failure Messages
Generic failure messages waste debugging time:
# Poor practice
with check:
assert data["value"] > 100 # What value? What's expected?# Better approach
with check:
assert data["temperature"] > 100, \
f"Temperature {data['temperature']} below minimum threshold 100"
assert data["pressure"] > 980, \
f"Pressure {data['pressure']} below safe minimum 980 hPa"Mixing Frameworks Incorrectly
Different soft assertion libraries have different verification triggers:
# Won't work as expected
from assertpy import soft_assertions
from smart_assertions import verify_expectationswith soft_assertions(): # Uses assertpy's context manager
soft_assert(x == 1) # Uses smart-assertions (wrong!)
# Failures won't be collected properlyIntegration with CI/CD Pipelines
Soft assertions integrate seamlessly with continuous integration systems. Configure pytest to generate detailed reports:
Modern development teams, including those working with mobile app development teams, rely on robust CI/CD pipelines to maintain code quality. Soft assertions enhance these workflows by providing comprehensive test feedback in every pipeline run.
# .github/workflows/test.yml
name: Test Suite
on: [push, pull_request]jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Install dependencies
run: |
pip install pytest pytest-check pytest-html
- name: Run tests with detailed reporting
run: |
pytest --html=report.html --self-contained-html \
--check-max-tb=5 --check-max-report=20
- name: Upload test results
uses: actions/upload-artifact@v2
with:
name: test-results
path: report.htmlMeasuring the Impact on Test Efficiency
Track these metrics to quantify soft assertion benefits:
- Average failures per test run: Should increase initially as more issues are discovered simultaneously
- Test run iterations to fix: Should decrease significantly (target: 30–50% reduction)
- Time to fix all failures: Total time from discovery to resolution
- CI/CD pipeline duration: May increase slightly per run but decrease overall due to fewer runs
Example measurement approach:
import pytest_check as check
import timeclass TestMetrics:
failure_count = 0
@pytest.fixture(autouse=True)
def track_failures(self):
TestMetrics.failure_count = 0
yield
print(f"\nTest discovered {TestMetrics.failure_count} failures")
def test_with_metrics(self):
with check:
TestMetrics.failure_count += 1 if not condition1 else 0
assert condition1
TestMetrics.failure_count += 1 if not condition2 else 0
assert condition2Advanced Techniques and Patterns
Parameterized Testing with Soft Assertions
Combine pytest’s parametrization with soft assertions:
import pytest
import pytest_check as check@pytest.mark.parametrize("user_id,expected_role", [
(1, "admin"),
(2, "user"),
(3, "moderator"),
(4, "guest")
])
def test_user_roles(user_id, expected_role):
user = fetch_user(user_id)
with check:
assert user is not None, f"User {user_id} not found"
assert user.role == expected_role, \
f"User {user_id} role mismatch"
assert user.active is True, f"User {user_id} not active"Fixture-Based Soft Assertion Setup
Create reusable soft assertion fixtures:
import pytest
import pytest_check as check@pytest.fixture
def soft_check():
"""Provides configured soft assertion context"""
return checkdef test_with_fixture(soft_check):
data = get_test_data()
with soft_check:
assert data["field1"] == "expected1"
assert data["field2"] == "expected2"Data-Driven Validation
import pytest_check as checkdef test_validate_all_users():
users = fetch_all_users()
validation_rules = [
("email", lambda u: "@" in u.email, "Invalid email format"),
("age", lambda u: u.age >= 18, "Under minimum age"),
("verified", lambda u: u.verified is True, "Not verified"),
]
for user in users:
with check:
for field, validator, message in validation_rules:
assert validator(user), \
f"User {user.id}: {message}"Troubleshooting Common Issues
Soft Assertions Not Collecting Failures
Problem: Assertions still stop at first failure
Solutions:
# Ensure you're using the check context manager
with check: # Correct
assert condition# Or direct check functions
check.equal(a, b) # Correct# Not this:
assert condition # Still hard assertion outside contextUnclear Failure Reports
Problem: Can’t identify which assertion failed
Solution: Add descriptive messages
with check:
assert user.age >= 18, \
f"User {user.id} age {user.age} below minimum"
assert user.email_verified, \
f"User {user.id} email not verified"Performance Degradation
Problem: Tests running slowly with many soft assertions
Solutions:
# Limit traceback generation
pytest --check-max-tb=1# Group related assertions strategically
with check: # Lightweight checks
assert field1 == value1
assert field2 == value2# Heavy operations outside soft assertions
computed_value = expensive_operation()
with check:
assert computed_value == expectedFrequently Asked Questions
Can I use soft assertions with unittest?
Yes, libraries like assertpy work with unittest. However, integration is less seamless than with pytest:
import unittest
from assertpy import assert_that, soft_assertionsclass TestExample(unittest.TestCase):
def test_with_soft_assertions(self):
with soft_assertions():
assert_that(1).is_equal_to(2)
assert_that("hello").is_equal_to("world")Do soft assertions affect test execution time?
The performance impact is minimal. Modern soft assertion libraries are highly optimized. The overhead of collecting failures is negligible compared to the time saved by eliminating multiple test runs.
Should I always use soft assertions?
No. Strategic use is key:
- Use soft assertions: For validating multiple independent properties of the same object
- Use hard assertions: For critical preconditions, sequential dependencies, and security checks
How many assertions should I include in one soft assertion block?
There’s no hard limit, but best practices suggest:
- Group 5–15 related assertions per block
- Break very large validation sets into logical groups
- Consider readability and debugging efficiency
Can soft assertions be used in production code?
Soft assertions are designed for testing. Python’s assert statements are removed when running with optimization (python -O), so they should never be used for production validation logic.
Conclusion: Transforming Your Test Automation Efficiency
Learning to use Python to implement soft chain assertion represents a fundamental shift in test automation strategy. By collecting all failures in a single test run, you provide your development team with comprehensive diagnostic information, dramatically reducing debugging cycles and improving overall productivity.
The pytest-check library offers the most robust solution for pytest users, combining ease of use with powerful features and active maintenance. For teams using other frameworks or requiring custom behavior, assertpy provides excellent fluent API support, while custom implementations offer complete control.
The key to success lies in strategic application: combine hard assertions for critical preconditions with soft assertions for comprehensive validation. Structure your tests to maximize information gathering while maintaining clarity and debuggability. With these techniques, your test automation suite will become more efficient, your debugging cycles shorter, and your software quality higher.
Start implementing soft assertions in your next test suite and measure the impact on your development workflow. The reduction in test run iterations and the comprehensive failure reports will quickly demonstrate why this technique has become a standard practice in professional test automation.










