Skip to main content

Advanced testing of Golang applications

Golang has a nice built-in framework for testing production code and you can find many articles how to use it. In this blog post i don't want to talk too much about the basics, table driven testing,  how to generate code coverage or detect race conditions. I would like to share my personal experiences with real world scenario.

Go is a relative young and modern programming language on one side, and it is an old fashion procedural language on the other. You have to keep in mind that fact when you are writing production code from the beginning, otherwise your program should became an untestable mess so easily. In the procedural way your program is executed line by line and functions calls other functions without any control of the dependencies. Hard to unit test, because you are testing underlying functions too, which are side effects from the perspective of testing.  It looks like everything is static, if you are coming from object oriented world. There are no dependency injection frameworks and/or inversion of control  (of course you can implement them, if you want to abuse the language, but it's a different story). The $1000 question is how to write testable Go code, and Dependency Injection Design Pattern (DIDP) is the answer. What does it mean? You have to pass over all of your side effects as input parameter of the functions. Sounds trivial, but believe me it's tricky to solve on the nice way or so tiring to refactor the existing project later on.

Without applying DIDP the only proper way to test complex Go source is monkey patching. I found one library called monkey, to do this. I don't suggest to use it at all. Change running code on the fly is a bad practice from the view of testing. On the other hand i can imagine situations where there are no other options.

Let's see how DIDP works. In my first example i would like to open a file and than read the first byte of the file. It looks like something this (without any proper error handling, just keep it simple):

We have many options to reorganize the code to make it testable, and i would like to cover most of them later, but first let's see the easiest one. Functions are first class citizens in Go and our side effect is a function call above, so it is straightforward to pass the function as input variable:

Our new function doesn't know how to read a file, it gets  reader and the implementation is out of scope what causes very easy testing:

It's clear now what is DIDP (sorry for boring you), so time to talk about the options what we have in Go.

Mocks and fakes. Go has a few existing mocking framework and they have good integration with built-in framework. The reason why i don't like them is all depends on code generation, which requires an extra step in the development cycle. Here is a list of the most famous frameworks:
Functions and interfaces. As i mentioned functions are first class citizens and gladly Go has a really awesome dynamic interface implementation system. Why not use them? They don't require code generation, they are built-in, and we have full control of the behavior without replacing production code runtime. Function parameters are nice for simple features and interface parameters for the rest.

My example is part of a CLI tool. It retrieves a token, fetches a credential by name and prints the ID of the credential.

There are three side effects in the code.
  • Authentication process -> we simply move out of the implementation
  • CLI context argument finder (c.String) -> become function parameter
  • REST call to fetch credential -> become interface parameter

Let's write the test code:

In the previous solution we only tested the output of the function. What can we do if we are interested in for example the input parameter of the GetCredential() call? Things become a bit complicated at this point, but we can solve them by channels:

As you see built-in features of the language are enough to solve most of the cases, and not need to introduce external framework  into the project or increase the development process with extra steps.

Comments

Popular posts from this blog

First impressions of the new Cloud Native programming language Ballerina

Nowadays everything is Cloud Native; everybody talks about CN tools, frameworks, solutions, and so on. On the other hand those tools totally changed the way we design, develop, test and release modern applications. I think the number of issues which we solved with the new concepts is equal with the number of new challenges, so in short we simply shoveled problems from one hole to the other. Many new tools appeared on the market to make developers' life easier by
integrating software with the underlying infrastructurewatching file changes and building containers automaticallygenerating resource descriptors on the flyallowing debugging in a running containeretc. Next to the new tools, new programming languages such as Metaparticle, Pulumi or Ballerina have been born. The last one had my attention because others are extensions on top of some existing languages, while Ballerina is a brand new programming language, designed from scratch.����…

Connecting non-Kubernetes nodes to Calico overlay network

Kubernetes networking has some basic rules.  In short, every pod has to communicate to every other. Selecting the right network plugin for the cluster is a critical key component when planning and architecting a new cluster. Luckily there are great presentations and blog posts around the topic of Kubernetes cluster networking on the internet, but the available sources are very limited about how to connect external resources that aren’t part of the cluster into the mesh. It all depends on what we would like to achieve, so finally we have to glue the solutions together.

In this post I would like to tell our story @IBM about converting an existing node to become a full member of our Kubernetes + Calico network.
First of all we had to specify the main goals:
Make node full member of the overlay network External node needs a pod IP to be able to reach it like any regular pod in the systemThe pod IP must be listenable for services on the external nodeService discovery is mandatory for both …