Before taking our first steps in creating unit tests in GO, it is important to bear in mind that in this article we are going to explain and present white box and black box tests. Before moving forward we recommend reading about GO – Rest API with DI.Now, are you ready? Let’s start creating unit tests in GO.
Unit Tests in GO
Should we test our code? I don’t think we even need to ask that question, in my opinion all our code should be tested.Leaving that behind, let’s move on to the important aspects of GO.First of all, it is important to point out that
in GO there is no test framework like in xUnit, NUnit, MSTest (C #), or JUnit (Java).Using standard GO code is always “recommended”. However, there are many good libraries that can assist us in writing unit tests quickly, and even intuitively, especially when the logic of our software is complex.
In this repository you can find the code from this article.
GO in a Real Case
To learn how to test with GO we are going to use a real-world example. In this article, we will be using the request of a client who wanted to build a web system for an educational establishment.Sounds like a good idea, right? So let’s use this example to delve a little deeper.
The goal of our project is to test a high percentage of our code, and in order to reach our goal we will take the services of the CRUD operations and apply white box and black box tests to increase our “coverage”.The first thing we need to do is explain the nomenclature and structure of the project. Since it is based on DDD, we will put our test cases in the same level or folder as the services, and the files must end in “_test.go” (due to a GO labeling issue).As for the methods in GO,
the tests must respect the following signature “TestName(t*testing.T)”, ie. the word Test followed by a meaningful Name, and we need to use *testing.T, which is our helper, as a parameter.With this in mind we can now run our test.
Project structure:
Cases 1 and 2 of Testing in GO
- Case # 1: White box test with no additions (no external libraries, just standard GO code).
Using the Given, When, Then structure, we declare the values we expect to obtain, and also instantiate our service, call the method to be tested, and evaluate the expected results. If we run the following test, we get:
If we change one of the expected values and run it again we get:
How do we create the SetUp and TearDown methods in GO? Well, there is a method called TestMain(m*testing.M) which will allow us to define the calls before and after the execution of the test.
- Case # 2: Black box test without additions.
As for black box tests, they do not belong to the package that we are testing, therefore, the way to accomplish this is to simply create them within another package:
As the image shows, the test was created within the package “deleting_test”, making it a black box test, which indicates that we do not have access to the internal methods of the package, and that we must indicate each reference with its package. For example, if we want to access the Student struct in the deleting package, we must do so like this: deleting.Student
GO Test Case # 3
- Black box test using Testify.
Testify is a set of tools that helps us perform unit tests simply. Among its tools we can find:
- Assertion, which gives us a simple tool to carry out validations without having to write the “ifs”
- Mockery, to “mock” the services, although we won’t use them here since we are using dependency injection, and therefore don’t need to mock any services
- Suite, which allows us to generate the test tools, and provides the BeforeEach, AfterEach, SetUp and TearDown methods (with other names, you can consult the API here). In this example, we do not need to use it as we have our DIY SetUp() and TearDown()
This would be our test without using the toolkit:
And this would be the same test using Testify’s assert tool
As you can see, although the change does not seem like much in principle, when we have many assert cases, it would be advantageous to change 3 lines of code for 1.
Bonus
Another interesting topic is “Immediate Failure” and “Non-immediate Failure”. The former is used when we want to cut the execution of test cases, for example, if we need to access a DB, or need to use a file that does not exist, then we cannot continue testing (assuming that proving the existence of said resource is not the objective of the test).The latter, on the other hand, is used when we want to report a failure, but we want to continue testing, for example, when a record has been inserted, but the ID has been miscalculated.Immediate Failure:
Non-immediate Failure:
Bonus # 2
The differences between white box and black box:
- Firstly, being able to test each private method of the service (some will see this as an advantage, others as a disadvantage)
- Secondly, being able to import dependencies from other packages without falling into the circular reference problem, eg. if we are in the deleting package, and we import the storage package, which in turn imports the deleting package, then we have a circular reference problem
In closing, I hope I have managed to give a general overview of how to perform unit tests in a project in GO.There is still much more to develop, such as integration tests, table-value tests, and coverage tests, etc.If you have read the previous article (and if you haven’t, I recommend that you do), you should have a pretty good idea of how to create high-quality, professional GO applications.
About the Author
Mauricio Bergallo is a Systems Engineer with extensive knowledge in a variety of programming languages. Great experience and understanding with all aspects of the software development life cycle.