Writing automated tests is a lot like eating your vegetables. As a kid (junior developer) you don’t like them, but as you mature, you start learning about the benefits of getting in those good fibers, yummy 😋. Hopefully, you even acquire a taste for it.
Typical blogs about automated testing tend to show you the generic pyramid: 10% E2E tests, 30% integration/contract tests, 60% unit tests. Or something like that. Integration and unit tests are primarily good at identifying faults made by code modification. My favorite part about them are that they’re good at identifying bad design. Code that is easy to test is typically loosely coupled and highly cohesive.
There are also many more forms of testing: performance tests, smoke tests, mutation testing, etc. But have you ever heard about Architectural Unit Testing?
Architectural Unit Testing
Architectural ‘Unit’ Tests are automated tests that focus on enforcing:
- Code Organization. Let’s say you have a team agreement that states, “All repositories must exist within a package that ends with the name
- Static coupling/dependencies between the different elements of your program, such as functions, types, packages, modules, components, libraries and frameworks. You can test things like cyclic dependencies, dependence on third-parties, proper use of anti-corruption layers, etc.
- Architectural style. Your team might have agreed on some layered architect, maybe you’re using the ports and adapters.
- Cross-cutting concerns. You might want to enforce that ‘Services’ within your codebase are properly annotated with
@Securedor that all the public methods in Services log at least once.
By putting these tests into a build pipeline, you can speed up peer reviews and eliminate human error in the review process.
About the name, ehrrr, it’s not really traditional unit testing. Unit testing implies that you’re testing a granular unit (typically a class) of your codebase in isolation from other units. Architectural Unit Testing is doing quite the opposite. What we’re actually doing is a sort of ‘static meta-analysis’ on your codebase. But static meta-analysis testing is not as marketable as Architectural Unit Tests 😏. These tests can also co-exist within the same test suite as your unit tests.
A Java Layered Architecture
ArchUnit is one of the first tools we’ll look at. This is a test to verify the structure of your typical N-Tiered/Layered architecture.
All .NET Core Services should be authorized
This test is written with the library ArchUnitNET, a port of ArchUnit for Java. It validates that all services have the attribute
Authorize. Notice how you can compose bigger elements, such as ‘Services’, from smaller elements.
TypeScript business logic should not depend on the frontend
This example uses ts-arch to validate that they’re no dependencies on the UI from the business logic folder.
All .NET Core public methods must be tested (Custom Condition)
Most of the aforementioned tools also support extension. In this example, I used ‘custom conditions’ to make
Other Architectural Testing Techniques
Software Architecture does not only concern itself with simple maintainability rules, it’s much bigger than that. You typically design your architecture with select architectural drivers in mind, such as those listed in ISO 25010. They’re the ‘-ilities’ of your system and usually form the bulk of your architectural complexities, unless simplicity is your driving requirement 😉.
Neal Ford, Rebecca Parsons and Patrick Kua advocate for the use of fitness functions to further validate these ‘-ilities’ in their book Building Evolutionary Architectures. Fitness functions are simply mechanisms that enforce some sort of architectural characteristic or requirement. Architectural Unit tests are fitness functions that enforce architectural requirements from within your codebase. They’re often targeted at some sort of maintainability characteristic.
Some other fitness function ideas can be:
- Using a static code analysis tool like SonarQube to enforce code style rules. (Maintainability)
- Using software package metrics to gather objective data about your codebase, then enforcing rules in some build pipeline. (Maintainability)
- Employing Chaos Engineering tactics to validate your architecture at run time. (Reliability/Compatibility)
- Automated penetration testing on all your microservices. (Security)
- Using Lighthouse to detect problems with your web frontend. (Usability)
- Performing automated load/stress/performance tests with k6 or JMeter. (Performance/Reliability)
- Etc... You can enforce many architectural requirements by making use of fitness functions.
In this article, I’ve introduced you to the concept of Architectural Unit Testing. It's a tactic that focuses on enforcing certain architectural requirements on a codebase. We’ve looked at examples in the languages Java, C# and TypeScript. We’ve also briefly went over fitness functions, from the book Building Evolutionary Architectures.
Hopefully, you find this useful. Thanks for reading!
Connect with me ❤
I’m usually down for a spar session. Maybe even a new code homie?