how to use build tags to control GO testing with a GitLab CI use case
Tags: testing, tutorial, test, go
Published: 2021-10-03 20:09:39

## abstract
The importance of testing code in programming is undoubtedly a fact. There cannot be good quality development without proper testing coverage. Sometimes, however, it is necessary to distinguish the tests or even launch them grouped together rather than all at once. This is the purpose of these few lines: present a valid solution for GO developers who wants to run their tests separately. 

The use case I prepared involves a dummy GO project with test files. It has been pushed on a GitLab repository to run the Continuous Integration, while diversifying tests. This is the link to inspect the solution directly on [GitLab](https://gitlab.com/enbis/testex). 
### prerequisites
- basic knowledge of GO programming and testing
- basic knowledge of the GitLab-CI

## the GO project
Let's start our tour presenting the pilot GO project used to evaluate the usefulness of **build tags**. It is a simple Web Server with the sole purpose of handling http requests based on this URL query string `/?value=10`. Assuming the value is numeric, the web server returns a JSON payload with a success status and the value increased by one unit.
```
{"status":"success","result":"11"}
```  
In case of invalid request it returns JSON payload with a fail status and the error.
```
{"status":"fail","error":"strconv.Atoi: parsing \"10a\": invalid syntax"}
```
### the project layout
In this scenario, the GO project has two packages besides the main: `handler` and `helper`.
- [handler](https://gitlab.com/enbis/testex/-/blob/main/handler/handler.go): decodes the http request and returns the answer after processing the data received
- [helper](https://gitlab.com/enbis/testex/-/blob/main/helper/helper.go): exposes the functions to manipulate the data 

### the test files
The layout presented allows me to separate the context for the testing stage. 
The `handler` can be tested using the `net/http/httptest` GO std library package. The [handler_internal_test](https://gitlab.com/enbis/testex/-/blob/main/handler/handler_internal_test.go) contains the httptest functions NewRequest and NewRecorder to prepare the http.Request and evaluate the http.Response. This could be identified as a sort of **integration test**.
```
func TestHandler(t *testing.T) {
	var response Response

	req := httptest.NewRequest("GET", "/?value=10", nil)
	w := httptest.NewRecorder()
	Handler(w, req)

	res := w.Result()
	defer res.Body.Close()

	assert.Equal(t, http.StatusOK, res.StatusCode)

	data, err := ioutil.ReadAll(res.Body)
	assert.NoError(t, err)

	err = json.Unmarshal(data, &response)
	assert.NoError(t, err)

	assert.Equal(t, "11", response.Result)
}
```
The `helper` package contains the functions used to process the data. Those functions can be tested as a pure **unit tests**. This is the [helper_internal_test](https://gitlab.com/enbis/testex/-/blob/main/helper/helper_internal_test.go) created.
```
func TestConvert(t *testing.T) {
	tables := []struct {
		in  string
		out int
	}{
		{"1", 1},
		{"100", 100},
	}

	for _, table := range tables {
		converted, err := Convert(table.in)
		assert.NoError(t, err)
		assert.Equal(t, table.out, converted)
	}
}

func TestIncrement(t *testing.T) {
	tables := []struct {
		in  int
		out int
	}{
		{1, 2},
		{100, 101},
	}

	for _, table := range tables {
		converted := Increment(table.in)
		assert.Equal(t, table.out, converted)
	}
}

func TestToString(t *testing.T) {
	tables := []struct {
		in  int
		out string
	}{
		{1, "1"},
		{100, "100"},
	}

	for _, table := range tables {
		str := ToString(table.in)
		assert.Equal(t, table.out, str)
	}
}
```

## the build tags ( or build constraints ) 
What we produced so far is the Web Server and its test files. As a GO developer I just have to run the command `go test ./...` and my whole list of tests is executed. 
The point is that I cannot distinguish between `integration test` and `unit test` or even have a separate report in a GitLab pipeline.
Here is where the **build tags** become useful. On top of that, they are easy to integrate into the code. 
The syntax is a comment on top of the test file, with the special word *+build* followed by the keyword that identifies the tag. The constrains are injected into GO using *-tags* flag in the test command. For more details, here the [doc](https://pkg.go.dev/go/build).
Let's see what happens in practical terms:
- integration test: the chosen keyword is integration, so I need to add the comment `// +build integration` on top of the handler_internal_test file and run the command `go test ./... -tags=integration` to execute it. 
```
// +build integration

package handler

import (
	"encoding/json"
	"io/ioutil"
	"net/http"
	"net/http/httptest"
	"testing"

	"github.com/stretchr/testify/assert"
)

func TestHandler(t *testing.T) {
....
....
``` 
- unit test: the keyword in this case is unit, I need to add the comment `// +build unit` on top of the helper_internal_test file and run the command `go test ./... -tags=unit`
```
// +build unit

package helper

import (
	"testing"

	"github.com/stretchr/testify/assert"
)

func TestConvert(t *testing.T) {
```
- unit and integration test: tags syntax allows to use AND and OR, as well as negative expressions. To run both at the same time, the command to use is `go test ./... -tags=integration,unit`

## the GitLab CI pipeline
Last but not least, the integration of the build tags into the GitLab CI pipeline. In this case the purpose could be to see the result of the tests grouped by tags. The case presented here contains only two tags: integration and unit. The [gitlab-ci.yml](https://gitlab.com/enbis/testex/-/blob/main/.gitlab-ci.yml) file will have two stages, each with the proper script.  
```
stages:
  - unit_test
  - integration_test

unit:
  stage: unit_test
  script:
    - go test -v ./... -tags=unit

integration:
  stage: integration_test
  script:
    - go test -v ./... -tags=integration

```
Which brings this result:

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/aftj1522ugiwchoe29t6.png)  

Thanks to the build tags in the GitLab pipeline presented above I'm able to distinguish tests by context, run them separately and inspect the result individually.