Photo by Lukas Tennie on Unsplash

QA is not solely the responsibility of QA teams but a collaborative process in which developers play a pivotal role.

In software projects, Quality Assurance (QA) is not limited to verifying that the application functions as intended. QA also involves preserving the architecture, maintaining code quality, and ensuring the system’s resilience against future changes. Software developers are not just code writers; they play a crucial role in safeguarding the system both technically and architecturally.

In this context, unit and integration tests, along with architectural tests (e.g., ArchUnit), and the contribution of developer-written tests to QA processes are of great importance. Additionally, understanding how these tests complement manual and automated tests written by QA teams emphasizes the comprehensive nature of the QA process.

In large, long-term projects, maintaining the integrity of software architecture is critical. Architectural tests ensure that code adheres to defined architectural rules and principles, preventing technical debt and enhancing long-term sustainability.

Using ArchUnit for Architectural Tests

ArchUnit is a library for defining and testing architectural rules in Java projects.

Real-World Scenario

Imagine a microservices project with the following layered architecture:

• The Controller layer can only access the Service layer.

• The Service layer can access the Repository layer.

• No layer can directly access another microservice’s database.

A new developer on the team might unknowingly write code that accesses the database directly from the Controller layer, violating the architecture. This is where ArchUnit comes into play.

@AnalyzeClasses(packages = "com.example")
public class ArchitectureTest {

@ArchTest
public static final ArchRule controllers_should_only_access_services =
classes().that().resideInAPackage("..controller..")
.should().onlyDependOnClassesThat().resideInAPackage("..service..");

@ArchTest
public static final ArchRule services_should_not_access_controllers =
noClasses().that().resideInAPackage("..service..")
.should().dependOnClassesThat().resideInAPackage("..controller..");
}

These tests prevent architectural violations and preserve the structural integrity of the code.

The Role of Developer Tests in Cross-Checking

QA teams often write manual and automated tests to ensure quality. However, these tests usually focus on validating overall system behavior and rarely delve into code details. This is where developer-written unit, integration, and architectural tests become essential.

Tests Written by QA Teams:

• Manual Tests: Simulate real user scenarios.

• Automated Tests: Automate user journeys to validate overall functionality.

Tests Written by Developers:

• Validate the technical behavior and details of the code.

• Create a cross-check mechanism with QA-written tests.

Importance of Cross-Checking: Real-World Scenario

In an e-commerce platform, QA teams validate the order process using manual and automated tests:

• Automated tests validate the flow: “Add to cart -> Make payment -> Complete order.”

• Developer-written integration tests ensure technical correctness, such as stock validation, payment gateway integration, and logistics system interaction.

Without these technical tests, the order process might appear successful but fail at critical points like shipping integration.

Conclusion: Tests written by QA teams and developers together ensure both the system’s overall behavior and its technical integrity.

Future-Proofing Through Tests

One of the strongest aspects of tests is their ability to safeguard software against future changes. Developer-written tests immediately identify how future changes affect existing behavior, preventing regression issues.

Preventing Regression: Real-World Scenario

Photo by Rocker Sta on Unsplash

Consider an airline company with a system for calculating ground service charges at airports:

• The current system calculates a parking fee per plane and a service fee per passenger.

• Future feature: Peak-hour surcharge for services during high-demand periods.

While implementing this new feature, it’s critical to ensure that existing functionalities remain unaffected. Developer-written unit and integration tests validate the following scenarios:

1. Preserving Existing Behavior:

• Standard parking fees and service charges remain unaffected for non-peak-hour flights.

2. Correct Functionality of the New Feature:

• The peak-hour surcharge is applied only during specified hours and at specific airports.

3. Handling Special Cases:

• Cargo flights, for instance, are exempt from the peak-hour surcharge.

Unit Tests:

Each function that calculates fees is tested in isolation.

Integration Tests:

The entire system is tested to ensure that the peak-hour surcharge works correctly in applicable scenarios.

These tests ensure that the existing system behavior is preserved while validating the correct implementation of new features.

Conclusion: Developers’ Contribution to QA

QA is not solely the responsibility of QA teams but a collaborative process in which developers play a pivotal role. Developer-written tests protect the system technically and architecturally, strengthening QA processes.

• Unit Tests: Secure the smallest components of the code.

• Integration Tests: Validate the harmony between system components.

• Architectural Tests: Maintain structural integrity and prevent architectural drift.

Together with manual and automated tests written by QA teams, developer tests create a robust cross-checking mechanism, ensuring both overall system behavior and technical details are reliable.

In conclusion, developers’ role in QA extends beyond writing functional code. Their tests are investments in the long-term success of the software, ensuring it remains robust and resilient to future changes.

Enes Batur

Senior Software Engineer @TAV Technologies