How do I setup GitHub Actions for my Gradle or Android project?
Tags: actionshackathon, tutorial, kotlin, android
Published: 2020-09-16 14:28:31

Hello Human!

This tutorial is for you if

- ✅ you have an Android or Kotlin or Java project built with [Gradle](https://gradle.org/)
- ✅ 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](https://www.jenkins.io/) or [Travis](https://travis-ci.org/) or [CircleCi](https://circleci.com/). 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

{% gist https://gist.github.com/jmfayard/627f0b060be431b975ced6aeb556d473 %}

- Commit and create a pull request

After a short while, you should see this:

![image](https://user-images.githubusercontent.com/459464/93350696-3557eb00-f839-11ea-8190-4aea758722fe.png)


The GitHub Action is failing. 

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

The  next step is to follow the link ["Details"](https://github.com/jmfayard/gray-sky-weather/runs/1119349006?check_suite_focus=true) to see what is going on.

### Define the task `./gradlew runOnGitHub`

Here is the error you will found in [Details](https://github.com/jmfayard/gray-sky-weather/runs/1119349006?check_suite_focus=true) 

```
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 *root* `build.gradle.kts` or `build.gradle` file
- Register a Gradle task called `runOnGitHub` like this:

```kotlin
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](https://user-images.githubusercontent.com/459464/93350673-2ec97380-f839-11ea-9fa7-5755524a62c0.png)

- **Celebrate**

![https://media.giphy.com/media/duLHlK018ete6OsgaL/giphy.gif](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/](https://guides.gradle.org/writing-gradle-tasks/)

Read the docs about GitHub Actions

[GitHub Actions Documentation](https://docs.github.com/en/actions)

Read the docs about the Gradle Command Action

{% github https://github.com/eskatos/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/jmfayard/refreshVersions/tree/master/.github/workflows) 

[https://github.com/LouisCAD/Splitties/tree/main/.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

{% github https://github.com/thepracticaldev/DEV-Android/pull/100 %}

{% github https://github.com/raychenon/gray-sky-weather/pull/12 %}

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:

{% link https://dev.to/jmfayard/the-one-gradle-trick-that-supersedes-all-the-others-5bpg %}


### 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](https://user-images.githubusercontent.com/459464/93350985-7a7c1d00-f839-11ea-92f6-6e8ac1fad792.png)

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](https://user-images.githubusercontent.com/459464/93350983-79e38680-f839-11ea-8aef-3faffddc86aa.png)

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

  [Just Say No to More End-to-End Tests](https://testing.googleblog.com/2015/04/just-say-no-to-more-end-to-end-tests.html) 

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** ](https://blog.jetbrains.com/teamcity/2019/03/configuration-as-code-part-1-getting-started-with-kotlin-dsl/)

![teamcity](https://user-images.githubusercontent.com/459464/93350973-77812c80-f839-11ea-8b4f-a6a64a3c11b4.png)

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](https://gist.github.com/jmfayard/627f0b060be431b975ced6aeb556d473#file-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 :)