All-In-One Scriptless Test Automation Solution!
Why we choose refactoring?
Our client is a financial services company in the US providing retirement planning services as a key offering. They had a legacy core system that had undergone many changes since 2000s in multiple applications – used for processing retirement plans and retirement savings related processes. The legacy application was written in Java 1.4 and ran on an un-supported application server built using legacy codes.
Java 1.4 had to be switched to a supported Java version and updated to a contemporary application server. The application team was worried about making changes without a safety net of tests as the codebase had grown over time and had complex dependencies, both with internal and external applications.
The decision makers responsible for system modernization therefore had two options to modernize their existing code base. Either undertake a complete rewrite, which is essentially building a whole new application, or put some serious effort into refactoring current code and configuration.
The choice to rewrite is a big one, and it requires massive investments into new skill and new technologies. Hence, the client opted for a refactoring strategy to make incremental changes in the internal structure of software, thereby making it easier to understand and cheaper to make modifications while keeping the core functionalities intact.
Test Automation Tools: Our QA team uses a proprietary automation tool that integrates with Playwrite, an end-to-end testing software for web applications and Cypress for the applications Java code upgrade.
Static Analysis: The QA Team used this in-house automation tool to detect any potential issues introduced during the code refactoring. These tools can help identify unintended side effects.
Proficiency in Legacy Frameworks: Our QA and Test Engineers brought their hands-on experience in working with legacy frameworks such as VB.NET, Visual Basic, Visual FoxPro
Right Modernization Roadmap: We classified the requirement into part such as the follows and divided the testing and QA team accordingly:
During the software development lifecycle, the update undergoes extensive testing. However, several issues arise due to inadequate testing procedures:
Inadequate Test Coverage: The testing team focuses primarily on the new features (route optimization and customer notifications) but neglects to comprehensively test the integration points with existing systems.
Insufficient Load Testing: The updated software is not adequately tested for high load scenarios. The logistics application needs to handle peak times with thousands of shipments processed simultaneously, but the load testing is performed with a much smaller data set.
Uncoordinated Deployment: The update is deployed to the live environment without a proper rollback plan or sufficient coordination with other teams responsible for related systems.
Mock Data Discrepancies: During testing, mock data is used instead of real-time data. This leads to discrepancies when the system interacts with real data post-deployment.
Once the updated software goes live, the following issues occur:
System Crash During Peak Hours: The application experiences severe performance degradation and eventually crashes during peak operational hours due to unanticipated load. The insufficient load testing fails to reveal this critical issue during the pre-deployment phase.
Route Optimization Malfunction: The new route optimization algorithm contains a bug that wasn’t detected because the testing didn’t cover all edge cases. This results in inefficient routing, causing delays in deliveries and increased fuel costs.
Integration Breakdown: The logistics application fails to communicate properly with the warehouse management system, leading to discrepancies in inventory data. Orders are incorrectly marked as shipped or remain unprocessed, causing chaos in order fulfillment.
Notification System Failures: The new customer notification system sends incorrect or duplicate notifications. Customers receive multiple delivery confirmations and cancellations, leading to confusion and a surge in customer service inquiries.
Financial System Discrepancies: The application generates incorrect billing information, causing issues in financial reconciliation and leading to inaccurate invoices being sent to customers.
Our refactoring strategy has improved the internal structure of the code without altering its external behavior. Here’s how we ensured that the behavior of production code remains unchanged during refactoring:
Automated Tests: Before starting refactoring, we ensured there is comprehensive test coverage, including unit tests, integration tests, and functional tests. These tests covered all critical paths and edge cases in the code.
Baseline Testing: We ran all tests to establish a baseline before refactoring. This ensured that the code behaves as expected before any changes are made.
Test-Driven Development (TDD): Wherever possible, we used TDD to write tests before making changes. This ensured that the refactored code passed the same tests as the original code.
Incremental Changes: Make small, incremental changes rather than large, sweeping changes. This makes it easier to identify any issues that arise and ensures that the code remains stable throughout the process.
Continuous Testing: After each small change, run the full test suite to confirm that the behavior remains consistent. This continuous feedback loop helps catch issues early.
Behavior-Preserving Transformations: Ensure that each refactoring step is a behavior-preserving transformation, meaning that it changes the structure or organization of the code without altering its functionality or outputs.
Refactoring Patterns: Use well-known refactoring patterns, such as extracting methods, renaming variables, or simplifying expressions, that are designed to be safe and behavior-preserving.
Branching: Use of version control to create a separate branch for refactoring was an all-encompassing strategy. It isolated the changes and allowed reverting to the original code, if necessary, without affecting the production code.
Committed Often: Committing changes frequently, along with passing test results, created a clear history of what was changed and ensured that each step maintains the code’s behavior.
Code Reviews: We had additional developers to review refactoring changes to ensure there are no unintended behavior changes. Peer reviews helped catch issues that might be missed by automated tests.
Pair Programming: We engaged in pair programming during refactoring. Having additional developer to work alongside testers helped ensure that changes remain behaviorally consistent.
Refactoring Logs: Our QA Team kept a log of all refactoring activities, including what was changed and why. This helped track the rationale behind the changes and makes it easier to review and verify that the code’s behavior has not been altered.
Update Documentation: Ensured that any changes to the code’s structure are reflected in the documentation, particularly if the refactoring improves or alters how the code is understood.
Feature Flags: Whenever refactoring involved changing or introducing a new functionality, we used feature flags to toggle the new code on or off. This allows us to safely deploy the refactored code and test it in production without exposing it to all users.
Staged Rollout: We gradually rolled out the refactored code using feature flags, starting with a small subset of users or environments. Monitor for any issues before fully enabling the changes.
Canary Releases: Our QA team deployed the refactored code to a small segment of the production environment (canary release) to observe its behavior in a controlled manner. It helped us identify any unexpected issues before the full deployment.
Monitored Production Metrics: Our QA and Test Engineers an eye on production metrics (e.g., performance, error rates) during and after the refactoring process to quickly identify and address any anomalies.
Prepare Rollback Plan: We had set up a pre-meditated rollback plan in place before refactoring. So that if at all anything goes wrong, we can quickly revert to the previous, stable version of the code.
Backup and Restore: Our QA and Test Engineers had ready mechanisms in place to restore the previous version of the code and data if necessary.
Download More Case Studies
Get inspired by some real-world examples of complex data migration and modernization undertaken by our cloud experts for highly regulated industries.