As developers, we want to provide and have the best experience in our apps.
We want users to love and talk about our app with great passion, as well as we want to have a pleasant and great time when developing it.
What are the key things to have a great app in the users point of view?
- No crashes.
We all hate when we’re doing that important thing, that take many steps to be accomplished, and suddenly the application breaks and we have to start all over again. We feel frustrated, and normally tend to go through other paths or apps if ever wanted to do the same thing again.
Being sure that the system is stable, your information is safe and the state is kept is crucial to maintain a relationship of trust between the product and the client.
If you’ve ever done something before and it disappeared somehow when going back to the app, you’ll think twice before using it again to do the same thing.
Every time you open the app the screen is different, either it’s missing some information here, or there’s an odd text there, you get confused and are not sure how it will behave in the future. Well, it’s obvious you won’t love an app that you feel that there’s a great lack of quality.
- Great and new features.
You like the app very much, it keeps what it promised to do, it’s beautiful and easy to use, and it keeps getting better and better as it adds more awesome features. You realize that you love that app, and you tell everyone about it. But even with all those points satisfied, the life of that specific app developers aren’t necessarily good as the client using it. So
What are the key things to have a great app in the developer point of view?
- Having great APIs.
Developing an app is great and fun. But as your application grow, both in age and in size/features, a not well planned architecture, coupled code and bad decisions will make it harder and harder to do fast changes and easy modifications and improvements without hurting the existent functionality. And this follow fear to modify the code, less time to develop new stuff and removes most of the fun.
After spending a single day, or even more, to figure out why a certain crash exists, debugging inside countless method calls to find where is the bad logic and feeling exhausted by it. Really not motivating at all.
- Be fast.
It's a great deal when that expected feature come out in no time, everyone on your company, and your users, loves it and you’re recognized by the amazing work you did. Your next thought is “Ok, so when do we start the next thing?”, it’s just amazing! So, okay, those things make sense, I’ll not be happy working in a place where I have to do boring stuff all the time, I wanna have an excellent app with exciting features and be able to work on the next ones as if it’s always day 1.
And I believe tests are great and crucial path to achieve this.
How do tests work?
A simple and brief description of what a test means is the warranty that something works as expected, 100% of the time.
It’s simple as that, you want to check a functionality and be sure it behaves as expected. You could always do it manually thought, a lot of companies out there rely only on that, but who’s been there know that it’s not scalable at all.
First, you cannot test isolated components, as you only have the entire application at your disposal.
Second, you need to have a really well documented test suit, to be sure to cover all cases possible, every time, and keep adding more as the application grow. You can imagine after a few years or even months the size of this document and what a boring job to follow and maintain it.
Third, you have no feedback at the development phase, so you’ll probably know that it’s not completely right too late and have to redo a lot of things again.
Automated tests remove all this noisy process, and also add more benefits than only knowing that your application works, as we’ll see later on.
Unit tests, as the name says, are meant to test single units of code. They tend to be really fast and small.
Integration tests are meant to test the integration of many decoupled (if possible) units of code, to assert that they do communicate as expected and no side effects occurred. They are normally bigger than unit tests, and often also demands more time to run.
End to end tests are meant to test a single feature of the application from beginning to end, including the communication of the application with other applications, such as other services, the database and so on. This means they are the more time consuming and larger in terms of lines of code.
The test pyramid
This is a typical diagram showing how tests should be distributed in a system.
As unit tests are smaller and run very fast, you wanna have the vast majority of your tests being unit, testing all single pieces of code on their on and making sure they work very well on their own, both on success and failure phases.
Integration tests should follow next, but as they test the right communication between those pieces, they exist in smaller numbers, which is great as they’re more expensive to run.
End to end tests, as they’re very costly to run, should cover only features entirely, not necessarily covering all the problems that could occur deep inside the system, as those are already unit tested. You should think about them as big big integration tests.
Different from common Java applications, tests in Android are separated in two types:
Are run directly on the JVM, meaning they do not know or need the Android OS to work. With this in mind, they are very fast to load and run.
- Instrumentation tests.
These types of tests require the Android system in order to run, so they have a bigger overhead in terms of initial time to boot
It’s common that people think that all tests on test are unit, and all tests on androidTest are integration tests, but that’s not always true. Though that’s often the case, there are integration tests that can be run directly on the JVM (android independent business logic aggregation tests, for example) as there are unit tests that can only be done by instrumentation (if you’re testing a class that open files, for example).
Inverted test pyramid
I’ve seen a bunch of applications putting a lot of code inside activity, fragments or service classes. This implies having all business logic code in one place, giant tests classes, no unit tests at all, as there aren’t single units of code that takes care of a single responsibility, and all tests need to be instrumented, as they depend on the android system to run.
This leads to very time consuming, hard to maintain and an almost fully integration tests suits. It’s what we call inverted test pyramid (integration tests > unit tests)
Fortunately the dissemination of Clean Architecture and the large adoption of patterns such as MVP and MVVM help to escape this mess, so I’ll not go deep into this subject.
Well, enough about explaining what tests are and their main concepts, let’s see how they solve real life problems and help you have a scalable app and a happy life as a developer, by tackling, but not limited to, those three points mentioned before:
- Good APIs
- Small maintenance cycles
- Focus on new features
Tests lead to good APIs
If it is easy to unit test your class, it probably means that it has a great API.
The key point here is to pay attention when it’s hard. It’s common to think of clever ways to get past this or that challenge, mock a bunch of behavior, Thread.sleep here or there and not really stop and think why it is so hard to get that test to pass.
Bad and hard tests are clearly indications that your API can be improved and will be hard to use this class on the application itself.
A great approach to get past this problem is TDD. Writing your tests before even creating the API itself will normally lead the cleaner and easier to use APIs.
This also means that tests lead you to have better documentation of code usage than leaving a bunch of comments in the code itself. With tests you can get rid of those annoying comments, explain the purpose of that code with tests and helper methods names and even prove that it does what you say it should do. Win win win situation.
I personally like to follow a given when then pattern.
This means that your tests should have an initial state, something that triggers the state change, and the expected resulting state. If there is no initial state, than the given part can be removed.
I personally like to use either one of the following approaches:
- Having the test method name tell you the states
- Having helper methods tell you the states:
In my experience, the former is often better for simple tests, as it’s easier to review, cause you don’t need to keep going to other methods and back to check the test logic. But it’s arguably that the latter is easier to read and understand, specially when there’s more than one given and/or when required.
When the two above points are working well, another great benefit that test gives you is a great architecture to work with. It becomes very easy to follow SOLID principles, keeping classes small, with single purpose, easy to use in more than one place.
Your app starts to be very scalable and easy to work with! Which leads us to the next topic.
It’s not like having tests will keep you from ever debugging code and try to find errors again. The world isn’t perfect :) You make mistakes, the developers of the OS and the sdks you’re using make mistakes, and your users *will* find them.
But with time, as the application gets more and more stable, your architecture is ok, you know exactly where everything belongs, you have good APIs and all of that tested, it becomes very easy to find out most of the problems, why they weren’t working (which is often a possible state you didn’t think of) and fix them.
I like very much in these cases to write a test that is able to reproduce the problem, before trying to fix it, and them make the test pass. You solve the problem and guarantee that if some change ever introduce it again, your tests will not pass.
It is a TDD approach of debugging, which helps you from focusing in fixing the problem and not messing around with other stuff. Also, it’s often a good post mortem too.
The point that most people complain more about start testing is that testing actually adds a lot of maintenance required, as the app is constantly changing, and fast, having to update tests demands time, the release delays, POs get frustrated and so on.
Well, good news for you, this is not entirely true. The points listed above already shown a lot of positives and valuables gains when testing, but more important, when done right, tests are not hard to maintain.
You should test only inputs, outputs and side effects. Your class can have a lot of transformations, have a bunch of private methods to compose the final result and so on, but it will still have inputs (either as constructor or method parameters), outputs (modifying an input (which is not good, but I’ll not discuss pure functions here as it’s not the point of this post) or returning something new) and/or side effects.
The inner functionality, or how it achieves the output, doesn’t concern the test. It is what we call black box testing. You put something and expect a result, but doesn’t need to know the details of how it was done.
If you decide to have a completely different approach on how to get that job done, having the same output, your tests shouldn’t have changed at all, and if it did, then your test is the one that could be improved.
With those thing in mind, it’s possible to have tests helping you a lot, not getting frustrated or feeling sad about having to write or update them, and focus on new and amazing features. Another Win win win situation! And let’s talk a little bit about that too!
Focus on New Features
Nothing new to say on how tests can get you to have a stable application and let your focus be on new features.
What I want to talk about here is for the ones that are still not convinced, thinking that it will be too hard to start tests now, that the application is already large, that it will take forever to have a good coverage.
Start small. Focus on new features to start adding tests. It’s always easier to test newly created things, where there isn’t too much logic applied yet. With all the points said before in mind, you’ll probably end up with a good architecture and little by little spread this culture for the rest of the app as well.
In the beginning, writing tests will take more time than none at all, obviously, but specially if you have no experience with it. Learning about mocks, setting up a good dependency injection pattern, etc…
But it will compensate afterwards when you’re working with an architecture you dominate, a new feature appears and you already get it drawn on your mind and you know exactly where you should start and how.
Well, I think that’s enough said about the benefits of having a test focused culture when developing an app. Remember that everything I’ve told here are things that work a lot with me, but that does not mean it’s perfect for you.
If you have other methodologies that you think are awesome when developing an app, that help you get productive, please share at the comments, I’ll love to hear about them!
And thanks a lot for reading!
Keep testing :)