Hello Human!

This tutorial is for you if

  • βœ… you have an Android or Kotlin or Java project built with Gradle
  • βœ… you write unit tests
  • ❌ you have no Continuous Integration system setup to run your tests on the server

Having the guarantee that your unit tests run on the build server is a keystone to establish a culture of testing withing your team - either at work or in an open-source project.

Perhaps you know that already, and have been told that you could setup Jenkins or Travis or CircleCi. But you don't even know which one to choose, let alone how to configure them.

My goal is to convince you that you can start today setting up a pipeline with GitHub Actions and Gradle.

My Workflow

  • Open your Android or Gradle project
  • Create a file called exactly .github/workflows/runOnGitHub.yml with this content
  • Commit and create a pull request

After a short while, you should see this:

image

The GitHub Action is failing.

That's progress: before it was not even running!

The next step is to follow the link "Details" to see what is going on.

Define the task

./gradlew runOnGitHub

Here is the error you will found in Details

Welcome to Gradle 6.6.1!

FAILURE: Build failed with an exception.

* What went wrong:
Task 'runOnGitHub' not found in root project '$YOUR_PROJECT'.

BUILD FAILED in 1m 31s

That makes a ton of sense, we didn't define the Gradle task runOnGitHub yet.

Next:

  • Open the rootbuild.gradle.kts or build.gradle file
  • Register a Gradle task called runOnGitHub like this:
tasks.register("runOnGitHub") { // 1
    dependsOn(":app:lint", ":app:testDebugUnitTest")  // 2 ==> CUSTOMIZE THIS LINE
    group = "custom"      // 3
    description = "$ ./gradlew runOnGitHub # runs on GitHub Action" //3
}

There are three steps

  1. you register a new task called runOnGitHub
  2. you define via dependsOn() which tasks should be executed before runOnGitHub. In this example we run the linter and the unit tests in the app module. This is the crucial step that will depend from project to project. So be sure to customize it for your needs.
  3. you document what will appear when you run ./gradlew tasks

Push to GitHub and make the Action pass

Once you have defined the task:

  • Run locally the task ./gradlew runOnGitHub and check it does what you want.
  • Tip: Save time by running
    ./gradlew --dry-run runOnGitHub to see quickly what tasks would be executed without actually running them.
  • Push to GitHub
  • Open the Actions tab on GitHub and if you are lucky you will see this after a while:

image

  • Celebrate

https://media.giphy.com/media/duLHlK018ete6OsgaL/giphy.gif

Submission Category:

Maintainer Must-Haves

Additional Resources / Info

To know more about creating your own Gradle tasks, follow the gentle tutorial at https://guides.gradle.org/writing-gradle-tasks/

Read the docs about GitHub Actions

GitHub Actions Documentation

Read the docs about the Gradle Command Action

Personal Story

Using GitHub Action to publish libraries

I have used GitHub Actions in the past for more complex workflows to publish libraries.

If you are interested, have a look here:

https://github.com/jmfayard/refreshVersions/tree/master/.github/workflows

https://github.com/LouisCAD/Splitties/tree/main/.github/workflows

But instead of talking about those complex ad hoc workflows, I thought it would be more interesting to help all Gradle and Android projects to make the first step to have continuous integration with GitHub Action and Gradle.

Yaml File or Link to Code

The nice thing about my workflow is that it's pretty generic.

I submitted my workflow in two Pull Requests, one for a friend's project and the other for the Android app of DEV.to

If you look at the pull-request, one thing I have done is to setup the Gradle build-scan for better reporting when tests are failing. I wrote abouit here already:

Is your Testing Pyramid Inverted?

A problem I encountered in the DEV-Android app is that it had very few unit tests

Something to keep in mind if you have an Android project is that my workflow allows you to run the unit tests on GitHub, but not the integration tests / automated GUI tests also called Android instrumentation tests.

There are good reasons for that.

Unit tests are fast, easier to write and lead faster to the discovery and fix of the bug when they fail.

This is why the recommended strategy is to have a testing pyramid where the emphasis is put on writing a lot of fast simple unit tests:

pyramid-ok

Unfortunately there is a common anti-pattern in the Android world where not enough emphasis is put on the unit tests. The testing pyramid looks like this and won't probably last as long as the ones in Egypt:

pyramid-ko

To understand why it's an anti-pattern, read the Google Testing blog:

Just Say No to More End-to-End Tests

Now if you want to start putting more emphasis on writing more unit tests, having this infrastructure in place with GitHub Action is a nice first step.

YAML is a terrible programming language

My biggest frustration by far was due to those YAML "configuration files".

I use quotes here because I think that "configuration file" here is a lie.

Look, we want to make a computer do stuff that are not trivial.

What we are doing is writing a programming script.

What happens here is that someone at GitHub just made up a terrible programming language disguised as YAML. There are hundred things that can go wrong, the IDE won't help you a bit, and it's a mmajor time waster.

The better alternative is to use an actual programming language.

Not Bash.

Just imagine how simpler it would be if the IDE could help you exactly as well as it helps you when you write your code.

Well you don't have to imagine because configuration as code is a thing in alternatives to GitHub Action for example in TeamCity from JetBrains

teamcity

I hope GitHub will provide us something like this.

Feedback?

I hope you will try setting up your first CI with GitHub Action + Gradle.

Copy the file .github/workflows/runOnGitHub.yml, define your Gradle task runOnGitHub and create your pull request.

Please leave a comment if that works for you. I usually only hear from what went wrong :)

This post is also available on DEV.