Crafting Resilient Software: A Guide to Clean Architecture, SOLID Principles, and Testing Excellence

Syed Ovais Akhtar
6 min readDec 14, 2023

--

In the dynamic landscape of software development, creating code that not only works but stands the test of time is a challenge that every developer faces. In the pursuit of building robust and maintainable systems, the trifecta of Clean Architecture, SOLID principles, and Unit Testing emerges as a beacon of best practices.

This article is your roadmap to crafting software that not only meets functional requirements but goes above and beyond by embracing architectural patterns that facilitate scalability, adaptability, and ease of maintenance. We’ll delve into the principles of Clean Architecture, where separation of concerns reigns supreme, ensuring that our codebase remains flexible to change while promoting high cohesion and low coupling.

Next, we’ll explore the SOLID principles — the bedrock of object-oriented design. By understanding and applying Single Responsibility, Open-Closed, Liskov Substitution, Interface Segregation, and Dependency Inversion, you’ll witness how software components become more modular, easier to extend, and less prone to unexpected side effects.

But the journey doesn’t end there. To ensure the integrity of our code, we’ll unravel the intricacies of unit testing. We’ll discuss not only the ‘how’ but the ‘why’ behind writing effective unit tests. You’ll witness the real-world impact of testing every layer of your application, from business logic to infrastructure, creating a safety net that allows for confident refactoring and continuous delivery.

So, whether you’re a seasoned developer looking to refine your skills or a newcomer eager to understand the foundations of resilient software, join us on this exploration of Clean Architecture, SOLID principles, and Unit Testing. Elevate your coding practices and empower your software to thrive in an ever-evolving digital landscape.

Let’s build a simple login screen for implementation.

User Interface

LoginScreen Composable for presenting Login Screen

Let’s build View-Model

Let’s build LoginUseCase

Let’s build UserDataMapper

Let’s build our LoginRepository

Let’s understand the architecture

Since we’are using clean architecture which is divided into the main parts i.e. Data, Domain, and Presentation

Data Layer

Our data layer is responsible for getting remote data like fetching some info from a server or database. So we added our repository in the data layer because it is responsible for getting data

Domain Layer

The domain layer is responsible for all the business logic like in our case it is responsible for managing user authentication. So we added our use-case in the domain layer because it is managing our business logic i.e. user authentication.

Presentation Layer

The presentation layer is the layer that is responsible for presenting data that has been fetched or anything related to a user. In our case, our LoginScreen composable, LoginViewModel, and UserUiDataMapper are added to the presentation layer.

Let’s see our project structure

Now let’s understand the SOLID principle by example

If we look into our UseCase or loginUser() function in our ViewModel we created responsibility for a single operation. Our UseCase is only responsible for managing authentications and our function is only performing login operations, nothing else.

Also, we haven’t created any class objects as we are using the dependency inversion principle so we injected all the classes along with the interface segregation methodology.

By choosing the right architecture and implementing SOLID principles our code is testable and maintainable.

Let’s deep dive into Instrumentation & Unit Test

Instrumentation Test( UI-Test )

let’s validate our UI by creating a fake reference of our ViewModel in Compose Tests.

As mentioned in the instrumentation code our compose test rule is generating UI for the test with LoginScreen() composable which we created for our app. Also, we had some tags in LoginScreen() composable with modifiers i.e. testTags(“some_tag”). Here we are using those tags to identify our component

We are performing some actions like writing a text on TextFields, validating the text of the Text Component, performing click operations on Button Components, etc.

Now let’s run these tests and see the output

Unit Tests

Now we are writing tests for our business logics and view-models

View-Model Tests

UseCase Test

Repository Test

Let’s run our tests

I used Mockito-Kotlin Library for Unit Tests for creating mocks for testing

Dependencies Used in Project

Mappers, States & Ui Data

Conclusion

In conclusion, our journey through the implementation of Clean Architecture, SOLID principles, and Unit Testing has been a compelling exploration into the art of crafting software that not only functions seamlessly but also stands resilient against the challenges of change and evolution.

By adopting Clean Architecture, we’ve embraced a modular and scalable design that fosters maintainability. The clear separation of concerns, from the innermost core to the outer layers, ensures that our codebase remains adaptable to new requirements without compromising the integrity of existing functionalities.

The application of SOLID principles has been instrumental in enhancing the flexibility and extensibility of our code. Through Single Responsibility, Open-Closed, Liskov Substitution, Interface Segregation, and Dependency Inversion, we’ve witnessed how object-oriented design principles contribute to creating code that is not just logically sound but also easy to comprehend, extend, and modify.

Unit Testing emerged as the guardian of our code’s integrity, offering a safety net that allows for fearless refactoring and continuous integration. By meticulously testing each layer of our application, we’ve instilled confidence in the reliability of our software, ensuring that changes to one part do not inadvertently affect others.

As we reflect on the intersections of Clean Architecture, SOLID principles, and Unit Testing, it becomes evident that these practices are not mere methodologies; they are guiding philosophies that shape the way we think about software development. They empower us to create software that is not just functional but also sustainable over time.

In embracing these practices, we’ve laid the foundation for software that is both an engineering marvel and an adaptable entity. The journey does not end here, though; it continues as we strive for excellence in every line of code we write, inspired by the principles that underpin resilient software architecture. May your future endeavors in software development be marked by clean, SOLID, and thoroughly tested code. Happy coding!

Follow me on GitHub and LinkedIn for more updates!

--

--

Syed Ovais Akhtar
Syed Ovais Akhtar

Written by Syed Ovais Akhtar

Senior Software Engineer - Mobile

No responses yet