Android Development Culture. The Document. #qualitymatters

Android Development Culture. The Document. #qualitymatters
Runner at the Finish Line Bronze Statue.

Android Development is in trouble (for about 7 years). Most of the projects do not have tests (unit, integration, functional); compilation and lint warnings ignored; code is kind of smelling spaghetti (hopefully, reactive-spaghetti) and so on.

Bad news: Google participated in such development culture right from the beginning of Android.

#perfmatters #qualitymatters

Yes, Google, #perfmatters, but #qualitymatters is something more important and should be done first.

Optimization without a good level of code quality is a kind of premature optimization, premature optimization is also known as the root of all evil (not in all situations, but mostly it is evil).

Good news: companies like Square, SoundCloud, Twitter and just some developers are trying to make Android Development better by giving talks and writing posts, many thanks to them. Also, looks like Google is finally interested in improving quality of Android apps: on AndroidDevSummit and other recent conferences we saw some content about testing, please keep it going!


It's time to fix Android Development.


Android Development Culture. The Document.

 

0. Fail fast, fail early

Why? — Because if you can catch problems before shipping the product to the users — you should do that.
Each item in this Document will follow this principle.

1. Pull Requests, Code Review and Continuous Integration

Project development should be done under version control system. Development process should happen via Pull Requests (PRs) with code review, nothing should be pushed to the main development branch directly. Each PR should trigger a build of the project on the Continuous Integration (CI) system. Builds should be reproducible, each member of the team should be able to build the project easily.

Fail early: If a build of a PR fails on the CI — PR should not be merged until fixed.

2. Code quality

Your code should be SOLID, or near to SOLID. It's totally up to you how you'll achieve that. It's not only about MVP/MVVM/MVC/etc, but also about each piece of code in each part of your app. Prefer writing pure functions and immutable objects.

Fail early: do not be the only developer in the project who writes maintainable code, make sure that others write good code too (talk to them, discuss this Document, motivate them!) — prevent bad code during the code review.

3. Static Code/Resources Analysis

Static code analysis allows you find issues in your code before you ship it to production. Also, it greatly helps on code review.

Android Lint, FindBugs, PMD, SonarQube, FB Infer, etc.

Fail early: run static analyzers during the CI and configure them to fail the build if project has warnings (not only errors).

4. Unit tests

Yes. Tests. Unit tests usually check that some function/object does its work correctly. The more tests and the greater code coverage you have in the project — the better and more stable product you ship to the users. In fact, most of the stupid bugs can be found via Unit tests, and of course if your app does some data processing — Unit tests will help you be sure that your code works correctly.

A short guide to Unit testing for Android projects:

  1. Prefer running Unit tests on JVM because it's much faster than running them on the device/emulator.
  2. Android Gradle Plugin can run your unit tests on JVM. Just put your tests into test/java_or_other_lang.
  3. You can run tests from IDE (right click on the test -> run) or from the Terminal ./gradlew test
  4. You'll soonly realize that Android SDK classes are stubbed and throw exceptions when you touch them in your Unit tests if you run them on JVM. That's sad but fixable. You can run tests that need Android SDK classes under Robolectric test runner and Robolectric will provide you working implementation of most of the Android SDK classes or usually a better approach is to abstract your code away from Android SDK classes.
  5. JUnit offers great runners and pretty good Rules concept, but JUnit assertions suck. You can use nicer assertions from AssertJ, Truth, etc and run them under JUnit (or TestNG/Spock/etc).
  6. If you need to check the behavior and mock some objects — you can use mocking libraries such as Mockito.

TDD or not — up to you, but definitely try it!

Fail early: run Unit tests during the CI and fail the build if some of the tests fail.

5. Code Coverage

As soon as you start writing Unit tests you'll need to know if your code covered well enough. You can use tools like Jacoco to check what code paths were touched by tests. Code Coverage is especially helpful if you're testing code which behavior depends on some conditions and you need to be sure that all possible variants of code execution were checked.

You can enable Jacoco via apply plugin: 'jacoco'. You can configure what classes/packages should be checked via jacocoReport Gradle task.

Configure Code Coverage tool to fail the build if coverage is not big enough. If you're just starting with Unit tests in an existent project — exclude non-tested classes and then remove them from the exclude-list as soon as you'll cover them with tests. This rule will help you make sure that new code is covered well enough. You can use jacoco-coverage plugin to fail the build according to coverage report.

Fail early: fail the build if Code Coverage is not big enough, make sure you check Code Coverage during the CI.

6. Functional (UI) tests

Yes. More tests. Functional tests check the functionality of your app from the user's point of view. Functional test launches your app and verifies that certain feature works correctly, ie loaded data displayed in the UI and so on. Most of the work that your QA team do can be automated via Functional tests, but this does not mean you don't need QA team.

There are two general approaches to run Functional tests. You can run them under Android Instrumentation or UIAutomator. Main difference here is that tests running under Android Instrumentation can work only with your application, they can touch your code and so on. Tests running under UIAutomator run under system process and interacts with your app via Accessibility APIs (which is much more limited than the abilities of the Android Instrumentation). If you need to test interactions of your app with other apps — you can use UIAutomator, but usually you can mock such interactions and test them via Android Instrumentation and your tests won't depend on external factors.

Recommendations:

  • Prefer Android Instrumentation and Espresso.
  • Page oriented architecture of the tests will help you write and maintain tests easier and faster. (ie when you have a Page class describing a Screen or part of the Screen of your app).
  • Mock interactions with the backend, it'll make your tests fully isolated, and you'll be able to run them in parallel and be sure that you're not sharing the state between tests. MockWebServer is a great helper here.
  • Developers should write functional tests, yep.
  • Teach your QA team to write Functional test — usually QAs think differently and know what cases need to be checked.
  • Check Code Coverage of the Functional tests.

Fail early: run Functional tests during the CI and fail the build if some of the tests fail.

7. Integration tests

Yes. Even more tests. Usually, Integration tests check how different components of your app work together: HTTP layer, REST API layer, Execution layer (RxJava, etc) and so on.

Imagine you have a class that uses a bunch of other classes and loads the data from a backend, then processes it and stores in the database. You should cover each class with Unit tests, but you can also cover such complex composition with Integration tests.

The main difference from the Unit tests here is that you're using not the mocks, but real implementations of the objects under the test. You can mock data transfer (MockWebServer) and database state and then run real code to see how it'll do its work.

You can run Integration tests on the device/emulator under Android Instrumentation or on the JVM as part of Unit tests, since tests on the JVM runs much faster — prefer JVM.

Fail early: run Integration tests during the CI and fail the build if some of the tests fail.

8. Developer Settings Menu (aka Debug Drawer)

Such menu in your debug builds allows you enable/disable tools like Stetho, LeakCanary, TinyDancer, mock/change some behavior of the app and so on.

Ability to change and inspect the app on the fly without need in changing the code will save tons of your and QA team's time.

Fail early: tools like LeakCanary will help you detect problems before you receive crash reports from real users into your analytics system. Teach your QA team to use such tools as a part of acceptance tests before each release.

That's it.

Please think about your development culture, discuss the topic with your team members and, as a result, your development process and quality of the product you're building can be significantly improved!

 


QualityMatters app

So, you're interested in a sample app that follows all these rules? There is a U2020 from Jake Wharton that follows most of these rules just by design. And here is QualityMatters app that follows all of these rules.

Hope you'll find something new and interesting:

  • Unit tests;
  • Integration tests;
  • Functional tests;
  • Static code analyzers;
  • Code coverage;
  • Developer Settings Menu;
  • MVP, RxJava, Dagger 2, Retrofit 2, …;

QualityMatters app will be maintained and updated.


Open letter to Google

As said before, #qualitymatters > #perfmatters:

  • Please post more content about testing!
  • Please test (unit, integration, functional) libraries and sample apps.
  • Android Needs A Simulator, Not An Emulator, because Robolectric is… it's helpful, no doubt, but there should be a better way.
  • Together, Google + Community, we can fix Android Development Culture.

We need Android Development Culture. And this is The Document. #qualitymatters


Related content: