Go With Go(Lang): Why Go?
Test Automation using GoLang
Once upon a time there were three experienced developers who were working on the new testing framework. They started it on one perfect spring morning, were working on it whole summer long, then fall had come and went… and all they had was sixteen automated tests. Flaky and unstable. Why did that happened? Part of the problem was that language they selected for automation did not fit their needs. It was popular language often used for automation, it just did not work well with multithreading and with asynchronous logic of software under test. The lesson they all learned was that language does matter. No need to say that I was the one of those developers.
So what will be the best automation language ever? The answer is — it depends. There are some things that you need to consider
- Can you use the same language as software under test? If yes — it is automatically makes it very strong contestant to use for test automation, but be careful — sometimes it is not the best choice.
- What is most important for you? Is it ability to work with huge datasets? Is it speed? Is it concurrency?
- Does language you selected have all the features you need? Tools? Frameworks? What will be output of software under test you are going to check? Does it have libs and easy ways to process that output? What is environment where your tests will be used?
- What about community? It may be the perfect fit, but if there are like three people in the world who use it it might be wrong choice. Sooner or later you will have a question or hit an issue and you need to have somebody who knows answer.
- Is it easy to learn? Do you need to spend a months learning before you will be able to automate one simple test? Will it be easy to train new member who just joined your team? Are there good learning resources?
So, why Go? Except having many wonderful features I will talk about later there are a lot of things that are checking boxes from the get-go (if you’ll forgive the pun):
- Go is easy to learn, even if you are not very experienced — somehow it is hard to produce extremely bad code
- Go is fast
- Asynchronous, Event Driven logic is easy to implement
- It has great community
- There are lot of tools and libs useful for automation (majority of them are open source)
What type of test can be automated using GoLang? You name it! Here is a short list:
- API tests
- System level End-2-End Backend Tests (it is possible to use GoLang for Front End automation, but I am not a UI person and have never used Go for UI tests. If you have experience or strong opinion about Go and UI please let me know. I’ll appreciate any input)
- Tests that work with Big Data
- Load Testing
- Integration tests
- CI/CD automation (Go is a language created by Google so it works great with Google Cloud Platform)
Another thing that I really like about GoLang is code maintainability. Go has very stable syntax, which makes it backward compatible. Also syntax itself is neat and clean.
Go has no classes, no inheritance. You can argue if it is a plus or minus. I think it is good from test automation perspective. You can change something and don’t worry that five generation down something has broken. The same goes with the constructors which also do not exist in Go. Remember that nobody writes unit tests for automated tests.
I also believe that test automation scripts should be scripts and they do not need all the complexity of the Object Oriented languages. It is my personal opinion no one needs to agree with.
There are no annotations in Go. Again, some will consider it as a minus but I prefer when no magic is happening behind the scenes and what you see is what you get when we talk about automated tests.
Go has static type system (vs Python) which means no weird errors because you are trying to use variable as string and it is an integer.
Go is portable. Code compiles into single binary(no need to install dependencies). It also works great on different OS. Tests that are automated using Go can be executed on Windows, Linux and MacOs.
Go is a Google child. Again it can be bad and good. We all know that sometimes Google abandons its toys, but for now it means that Go is moving forward, well supported and works great with Google products and solutions:
- GCP
- CI/CD automation(kubernetes, docker; by the way, both of them are written using GoLang)
- Messaging services (Pub/Sub)
- Data tools (BQ)
If tests performance means a lot to you and you work with huge datasets or care about response time Go is the top choice. It is much faster than Python (each goroutine is 10x cheaper in resources than python thread).
Test automation in Go starts with testing package. This package is enough to automate all the types of tests I listed above. testing package comes with Go by default and you don't need to setup or install it.
All GoLang testing frameworks(that I know about) use testingpackage and follow the same naming conventions and rules. To execute tests that use those frameworks you can use default go test command or framework specific command.
The command go test triggers the execution of any test function which is found in “*_test.go” files corresponding to the package under test. For a given file, say foo.go, the test is placed in a file called foo_test.go in the same package or a separate test package.
testing package does not contain assert functions so I would recommend to use some assertion package, for example testify. Testify is a set of packages that provides a lot of assert functions for verification of the test results and other useful features for test automation. It is easy to install and actively supported (last release as of this writing — June, 2018).
Let's take a look at the test example that verifies that after we hit an API endpoint /execute it returns 200 OK. Please note that httpRequest function used in example is not standard GoLang function.
func TestGetTesting(t *testing.T) {
url := fmt.Sprint("https://localhost:8080/execute")
resp, err := httpRequestTest(url, http.MethodGet, "")
// force test to fail if error
if err != nil {
assert.FailNow(t, "RequestFailed")
}
//else print response on screen
fmt.Println("Response:", resp.Status)
//and verify that we got 200 OK
assert.Equal(t, resp.Status, "200 OK")
}
testing package supports table driven tests. They might be good for unit testing but I do not recommend to use such format for any other automated tests. The further you are from code the less useful table tests are. Let’s take a look at example. This test verifies conversion of character to integer, one positive scenario and one negative.
func TestAtoi(t *testing.T) {
tests := map[string]struct {
input string
output int
err error
}{
"successful conversion": {
input: "1",
output: 1,
err: nil,
},
"invalid integer": {
input: "not an integer",
output: 2,
err: &strconv.NumError{},
},
}
for testName, test := range tests {
t.Logf("Running test case %s", testName)
output, err := strconv.Atoi(test.input)
assert.IsType(t, test.err, err)
assert.Equal(t, test.output, output)
}
}
The output of this table test will look like this:
$ go test TableTest_test.go
ok command-line-arguments 0.789s
All good and clear. Let’s force second case described in table test to fail by changing output to “2”
"invalid integer": {
input: "not an integer",
output: 2,
err: &strconv.NumError{},
},
and run it again
$ go test TableTest_test.go
— — FAIL: TestAtoi (0.00s)
TableTest_test.go:28: Running test case successful conversion
TableTest_test.go:28: Running test case invalid integer
TableTest_test.go:31:
Error Trace: TableTest_test.go:31
Error: Not equal:
expected: 2
actual : 0
Test: TestAtoi
FAIL
FAIL command-line-arguments 0.571s
As we see that whole test marked as failed. It’s totally ok when you are close to code(unit testing) and don't need to communicate test results to the team. If you need to do so tests tables make it very difficult. As we have seen there are two test scenarios and one of them passed, one failed. How should we report it? The output states that test failed, but there are two test cases table.
What if we have ten or twenty cases in one table test and only one corner case failed? Should we mark them all as failed? What should we tell the team? Table tests look neat from code perspective but it will be constant source of confusion if they are used anywhere besides unit tests.
This concludes first chapter of this article. Next time we will talk about other useful features that can be utilized in test automation and Go testing frameworks. Meanwhile here is the list of resources I used to prepare this article and recommend for future reading:
- Awesome Go — list of awesome Go frameworks, libraries and software.
- Basic testing patterns in Go by Sebastian Dahlgren
- The beauty of Go by Kanishk Dudeja
- Why Go is skyrocketing in popularity by Jeff Rouse
- Why should you learn Go? by Keval Patel
- 5 Reasons Why We switched from Python To Go by Tigran Bayburtsyan
- GopherCon 2018 — From Prototype to Production: Lessons from Building and Scaling Reddit’s Ad Serving Platform with Go by Deval Shah
- 5 simple tips and tricks for writing unit tests in #golang by Mat Ryer