“We just need the system to do XYZ when the user types “A” into the textbox.” We have all been given requirements like these. Tasks like these are commonplace for web development, and with ASP.NET Core MVC, its very simple to pull this off. The steps would look like:
Testing a scenario like this is easy too, right? Navigate to the site via a browser, type in “A” on the textbox and XYZ should happen. It’s a basic approach to verify something works once (i.e. right now). What happens if you start introducing new rules when the user types “B”? What about rules like typing “B” when another value is already set? Your basic test approach just got more difficult (in many cases it grows exponentially), takes more time to verify, and is subject to a person remembering to test for it.
Usually, on smaller projects, when you have this happen, you code it, test it quickly via the browser and move on. On a recent project, we had a simple area of the code that we did just that. Since our project stakeholders were working closely with us, in an agile team fashion, they saw the screen and started asking questions (mostly amongst themselves) very early on. They asked for changes, introduced new business rules, and turned this code into a very complicated area of logic. It became very apparent that testing via the browser was not going to be enough and would be time intensive. We saw the following issues:
Automated Testing to the Rescue!
Enter unit tests and integration tests. At Omnitech, most of our projects are set up with an automated build and deployment pipeline. Azure DevOps makes this incredibly easy to do and in most cases can be done with 5 – 10 minutes of work. The build pipeline, in particular, has a lot that you can do within it, of interest for this article, is running automated tests. These automated tests can assert that logic is working as intended and can “fail” the build if they do not assert correctly. On our project, we had the build and release pipelines setup, but we had no tests to speak of. We knew that writing the tests, getting the framework setup, scripting scenarios, and mocking objects was going to take a lot of effort and time. After weighing that time against the labor-intensive, repetitive, and error-prone web page testing, we took the gamble that writing automated tests would pay off in the end as the logic continued to evolve.
Our first task was determining how best to approach adding tests. This turned out to be less daunting than it sounded as Omnitech recently had a book club around the book “The Art of Unit Testing” by Roy Osherove that gives us many ways of adding tests. In addition to that, ASP.NET Core, with built-in dependency injection (DI) already gives us a head start on making our code testable. Our service layer, which contained our business rules, was loosely coupled with the frontend controllers and our data/repository layer. With this setup, our code was already testable but not entirely “unit testable.” I say this due to the fact that unit testable code should be all the following:
So we started down the road of refactoring our code using methodology from the book club and discussions spinning out of the book club. Our goal was to achieve as close to 100% coverage as possible from all of our unit tests and integration tests for ALL services/business rules (not just the really complicated areas). Our technical road map looked pretty easy once we got into the swing of things. The technical road map included using MSTest framework, EF Core in-memory database, and FakeItEasy for mocking. MSTest framework is robust and did everything we wanted in terms of attribute decorating and seamless integration with Visual Studio 2019. EF Core’s in-memory database capability and the fact that our DB context is also DI’d, allowed us to consistently build up and tear down our DB to test our logic scenarios. Utilizing the framework FakeItEasy, we were able to control any non-targeted unit test dependencies within each test. Overall, very little code had to be refactored to make the testing work.
In the end, due to time constraints, we ultimately had to settle for a lesser degree of coverage that focused on the most business-critical and obtuse areas of logic. The payout of the tests cannot be understated, however. The tests, once written for a business case, allow us to
We’re Learning and Improving at Omnitech
We are just starting to scratch the surface testing our projects in an automated fashion. Omnitech is looking further into how we can provide value to our customers through automated tests on not only business logic and compiled code, but also on front end frameworks and backend database stored procedures/functions.
Omnitech Automated Testing Manifesto
At Omnitech, we value solving problems for customers with high-quality solutions in an efficient way. We believe that solutions cannot be of quality unless they are tested. Therefore, we value testing that is automated, maintainable, consistent, and durable over manual testing, which may be tedious, repetitive, slow, error-prone and requires re-occurring human time.