ASP.NET Web Forms modernization series, Part 6: Testing, deployment, and operational considerations
This is the sixth and final part of our series of blog posts dedicated to ASP.NET Web Forms ModernizationThe series expands on the topics discussed in our Migration Guide: ASP.NET Web Forms to Modern ASP.NET whitepaper – a compilation of technical considerations for modernizing legacy .NET web applications, gathered from our experience at Resolute Software doing many .NET modernization projects.
Here is a list of all articles in the ASP.NET Web Forms modernization series:
Part 6 – Testing, Deployment, and Operational Considerations (you are here!)
The last article of the ASP.NET Web Forms modernization series will discuss testing, deployment, and other considerations during development and what your mind needs to focus on when migrating web systems.
We will also map how these topics have historically developed in the years and why it is essential to be aware of your project versioning for any of the tools in use. Тhe success of a software project is tightly related to the interoperability of all tools, their versioning, and the issues that might occur due to a non-optimal combination of tools and versions.
Testing is an integral part of the development process. In general, testing can be manual or automated. While opening a browser and clicking everywhere around your web application can be a legitimate approach in some cases, automated testing becomes crucial in the long run. As your app grows, solely relying on manual testing becomes tedious, takes longer, and increases the likelihood of making a mistake. Of course, you will always need real users to test your applications, but you want those tests to focus on the UX, the content, or some experimental features you are building instead of bug reports that automated tests could have caught early on.
There are many types of automated tests and techniques, but unit tests, integration tests, and end-to-end (E2E) tests are the three to focus on. In an ideal scenario, your application’s test plan will combine all of the above. Unit tests are high-speed to run and quick to write as they cover a tiny chunk of code/algorithm. While integration tests are slower, they ensure integration or communication between components, and E2E tests are the slowest to write but cover most ground as they ensure the correctness of the system as a whole.
The so-called “test pyramid” method is helpful when designing your test coverage. You want different granularity of tests and a different number of tests depending on their complexity and speed of execution.
Source: Marcotte, Carl-Hugo, Architecting ASP.NET Core Applications - Third Edition, E-book ed., 2024
Always consider the return on investment (ROI) from your testing effort. For example, running one integration test might be faster and make more sense than several unit tests. The time it takes to run your test suite is vital for receiving fast feedback. Be sure to take advantage of test runners like Visual Studio, VS Code, or the CLI and run your tests in a CI pipeline - receiving feedback on two levels ensures finding issues as quickly as possible.
At Resolute, we prioritize time efficiency and productivity and strive for maximum outcomes with minimal time investment. Our approach emphasizes the importance of Unit tests. This method proves most effective in targeting specific functional logic and focusing on it. Unit tests are particularly valuable for scrutinizing business logic where adherence is crucial and the potential for errors is most significant. To further enhance our testing efficiency, we strictly separate business rules into a business layer so operations are as smooth as possible and testing is quicker and easier to plan.
Integration tests are another type of test we leverage. As mentioned above, they tend to be slower and cover more "ground," so we only run them inside the CI/CD pipelines and in a precisely set testing environment. Integration tests are often related to functionalities requiring a data set and a 3rd party integration with APIs to be thoroughly tested. For all this to work as expected, we configure the environment and use it in the CI/CD pipeline.
Other testing types are planned individually and usually reserved for scenarios with a high risk of significant faults that could significantly impact the project's success.
Some testing strategies include load testing, performance testing, regression testing, contract testing, penetration testing, functional testing, smoke testing, and more. While automation will help you validate a wide range of the application's aspects, specific tests, such as UI tests, are more challenging to automate or more brittle than others.
Shifting focus to the tools integral to securing our deployment infrastructure. We strive to get rapid feedback on quality. In fact, the speed at which we identify existing issues is crucial, and finding them at the earliest stage possible makes it easier and quicker to fix.
When it comes to Continuous Integration, we rely on two primary platforms: GitHub pipelines and Azure DevOps. While we can debate for hours about which is better, the reality is that the choice is often dictated by prerequisites set by the customer's infrastructure. Our approach aligns with what our clients are already familiar with, what they currently use, and what would be easier for them. Both platforms are flexible enough to provide the best experience and tooling.
When talking about customer requirements, we should also mention containerization. What are the benefits of containerization? Packaging a solution into a container allows us to deploy it anywhere, regardless of local server configurations. Furthermore, containers statistically provide the best server utilization, meaning they are the most cost-effective approach. While Virtual Machines achieve 25% utilization on average, Containers achieve up to 40-50%.
When is containerization an overhead? It's tempting to package everything together and make it versatile so it runs anywhere.' There are cases, however, where this is unnecessary, and building containers creates functionality that is not beneficial to the system. These cases are often related to specific customer requirements or the software solution must be integrated into a more significant, complex architecture. For example, if the customer has an Azure Active Directory, he will most probably prefer to use Azure and is very unlikely to shift to another Cloud provider. In that case, we can directly turn to Azure App services to accommodate our solution.
Everything mentioned above factors into the cost of a software solution. Let's zero in on several aspects that could break the software development process. One of these aspects is relying too much on performance indicators or misreading their meaning. The code coverage indicator is one such example. Code coverage is a neat way to see how much of your code is covered by tests. Ideally, you should look for a big number - 60 - 70% or above. This number, however, does not tell you how well your tests monitor your functionality. In short, code coverage is nice- but does not guarantee your system's health and stability.
Measuring recurring regressions is another KPI to boost your confidence in your software project. Ideally, this indicator should be kept under 2%. That is, issues already taken care of and being fixed should not reappear more than 2% of the time. This KPI takes more configuration, so bugs are triaged and analyzed as different types, but it adds confidence in the progress of the development process.
Technical debt is another operational concern. Any corners cut short during development pile up into technical depth; ignoring edge-case scenarios in the code adds to technical debt. There's almost no way to avoid it, absolutely. The key is to keep it low.
At Resolute, we adhere to development best practices and leverage the best tools for the job to minimize development time while keeping code readability high. We follow the domain-driven development philosophy. We are guided by the specific customer business requirements and processes, deciding where to place a firm model contract and where to abstract. These considerations help us to balance the development process while boosting performance and quality.
DevOps is a pivotal piece of the success puzzle regardless of the project's scale. It could make the difference between achieving your company goals and falling into the rabbit hole of bugs and missed deadlines.