Basic GitHub Action Creation

Rivers Agile Software Engineer, Stephen Teodori, furthers his Continuous Integration series by creating a basic GitHub Action to build and test our project

In Part 1 of this series, we explained how to integrate Github Actions into your CI process. Now, let’s try creating a real example. For Part 2, we’ll be using a simple DotNet Core 3.1 project called “DoctorWhoAndWhen”. With this project, you can use an actor’s name and return the years they portrayed ‘the Doctor’ on the TV show “Doctor Who”. Alternatively, it can use a given year and return all actors who portrayed ‘the Doctor’ in that year. Our project will be stored in this GitHub repository. For Part 2 of this blog series, we’ll be using the basic workflow test branch, which will be preserved to match this post.

Github Actions

Over the course of Parts 2 and 3, we will be creating Actions that do the following:

  1. Build and unit test our code (Part 2)
  2. Package a release copy of our code (Part 3)
  3. Upload our packaged code to AWS (Part 3)
  4. Publishes a new version of a nuget package (Part 3)

We’ll have three actions in total when we’re done.

The first action will run on branches where we are working on new features. It’ll run any time code is pushed to the repo and will only run unit tests, though it will also appear later on in the pull request when you attempt to merge the branch. You can use an Action like this in several ways. A developer can use it to ensure everything is as expected before making a PR request. If the PR unit tests fail, you can also see that it wasn’t the feature branch, but the merged code that caused a failure. Alternatively, perhaps your full set of unit tests take 10+ minutes to run and disrupt your developers ability to work in the meantime. By having simplified local tests with more extensive GitHub Actions unit tests, you can free up your developer to work on something else while the tests run in the cloud.

The second action will run when a pull request is created and will again run unit tests. This is similar to the previous action with the important difference that the code being tested will be the merged result of the two branches. This is an important step if the incoming branch is missing changes from the base branch. As an alternative to this, you can use branch protection and set the “Require branches to be up to date before merging” option. Just make sure you select a status check or the option will be ignored.

The third action will run after code is pushed to our primary branch and will deploy our code to a storage location. This will effectively run whenever a pull request has been merged, but will also run any time changes are pushed to the branch. The result is that whatever state our primary branch is in, it will also be the state of our deployed code.

Build and unit test our code

For Part 2, we’re just focusing on the first of those actions. The complete file can be found here, but let’s break down its contents. If you’re not familiar with YAML’s format, it is very straightforward and you can read about it here.

First, we have name, which mostly just serves to label the workflow in the full list of your repo’s actions. Ours will be “Run unit tests for feature branch”, so it’s generally the summary of what we’re accomplishing and on what branches.

Next, we have on, which details the conditions that will trigger the workflow. In our case, we are saying that the workflow should trigger when code is pushed (push) to the branch. In addition, we only want to trigger this workflow on branches (branch) that start with “features/”. You’ll notice that we have two asterisks at the end (**) of the filter. Using “features/**” means that branches with the name “features/ticket-45_add_doctors” or “features/data/exaggerated/example/ticket-45_add_doctors” will match. If we instead used “features/*” only, the first name would be a match.

on:
push
:
branches
:
- 'features/**'

Afterwards, there is the jobs section, which includes the actual commands that will execute. We’ll only have one job run which we titled feature_test. You can name yours anything you like (within the bounds of alphanumeric along with underscore and dashes). This name will appear in the branch protection settings of your repo as well as in the GitHub action logs. Within each job, we set what operating system to run on using runs-on. You can use the link to see the full list of available operating systems.

jobs:
feature_test
:
runs-on
: ubuntu-latest

After we define the environment, we define steps which are the actual list of commands to execute. Commands will be executed in the order we list them. We start by executing a few setup commands. These have already been defined by someone else so instead of re-inventing the wheel we just execute the pre-packaged actions they defined. We’ll download a copy of our code into the environment by calling actions/checkout@v1 with the uses keyword. We’ll also download and install DotNet Core’s SDK into our environment with actions/setup-dotnet@v1 . We are also able to specify what version to install by using the dotnet-version argument. You can search for more available actions in GitHub’s Marketplace.

- uses: actions/checkout@v1
- uses
: actions/setup-dotnet@v1
with
:
dotnet-version
: '3.1.100'

Now that our code and required software is ready, we execute our unit tests with the run keyword. This executes the command as if it were entered in a shell interface. You’ll see we have a name defined as well, but it is optional. We also have the shell manually set to bash, but it will also use a default based upon the job’s operating system. The actual command we run is very simple. We tell DotNet to build and run our unit tests with the Debug project configuration.

- name: Test with dotnet
run
: dotnet test --configuration Debug
shell
: bash

And that’s all there is to running basic unit tests in GitHub Actions! Once that workflow is in your branch, it’ll begin monitoring for triggers without any additional fuss. Here you can see what the output looks like when the unit tests run successfully. I also provided an example of what it looks like when the code fails to build, as well as when the unit tests find a problem. In addition, each of the failed workflows sent me an email about the error. You can modify the messages you receive in your account’s notification settings.

Next steps

Now that we have this handy tool, we want to make sure we are leveraging it effectively, right? A simple (and powerful) example is running unit tests when pull requests are created. We’ll be exploring that topic and more in Part 3 of this blog series. If you’ve been following the Continuous Integration blog series, but still have questions about the process (or Github Actions), don’t hesitate to reach out. Our team can offer suggestions on how to implement Continuous Integration and streamline your development process. Contact us today.