Conf42 Golang 2022 - Online

Testing in Go: 101

Video size:

Abstract

Nowadays ensuring the quality of our artifacts is a must to support the continuous deployment process. This talk is about testing in Go, describing best practices and different approaches through practical examples.

Today is very common to heard about Test Driven Development (TDD), unit testing, integration testing and so on, but these practices still implies an effort to development teams.

The focus of this talk is to present some way to testing our apps in Go, describing best practices and different approaches so the audience can start testing their apps in a proper way.

This talk includes topics like: Why testing is important, where we should create our tests, how to mock dependencies, how to stub behaviors, use of standard testing libraries and checking for code coverage. The final part works as an introduction for Behavior Driven Development, checking for some modules that help us to implement acceptance tests in an idiomatic way (for example, ginkgo+gomega).

Summary

  • Andela will talk about testing using Go language. The main propose is to present some alternatives and good practices. Whether you are a beginner in the go language or an experienced developer, this talk could be quite useful for you.
  • Francisco Daines is a principal software engineer at Walmart Chile. He talks about why we should test our applications and the benefits of doing so. He also presents two approaches for planning our efforts, coding different kind of tests.
  • Why is testing important? To explain the importance of testing, I included this diagram proposed by Ken Beck. The more stress you feel, the less testing you will do. Three benefits of application testing: to reduce bugs, improve security and increase software quality.
  • There are two different testing approaches. Mycon focus the effort on have faster feedback from testing. Kensidots prefers to focus our effort in integration testing. There is no rule of thumb to select the right testing approach.
  • In this section we are going to look at the testing tool provided by go language and standard package that we can use to create unit tests. The testing package is includes as part of these Go language, one of the standard packages. We are only using the unit test provided by this package.
  • Go testing tool can be used to test multiple cases. A grown approach is to copy and paste the test, changing only the input parameters and the expected values. To avoid code duplication, the main idea is to define a table with expected values and translate into code.
  • The behavior will be exactly the same. But the big difference is that we need much less code. And it's very simple to add new test cases. We can identify the input parameters for each case. Table driven test is a good approach.
  • examples are code snippets that will be running as tests by go testing tool but also are displayed as package documentation. We can visit and see these examples in the Godoc web page for the package. This is very useful to create some examples and tests in line how to use a specific function.
  • To create an example, we need to send a value or a couple of values to the standard output. The standard output receives the number ten and that's it. The example will be published as part of the Godoc web page of this package.
  • Test assertions are sentences that must be true. There are a lot of assertion libraries, but I prefer the testify package. Allows us to check if two slice have the same values but not necessarily in the same order. What happens these we will get a more readable output with more details.
  • Test doubles are a replacement for some dependency to EC testing. The test double merely has to provide the same API as the real dependency. There are five variations for test doubles: dummy, fake, spice, stabs and mocks.
  • Use mocks to test software that depends on specific system. An interface will define all the API for every concrete dependency that our system needs. A mock is very similar to a stack, but tests will also verify that the object under test calls these mock.
  • There is a convention in go. The go testing files should always be located in the same folder and package where the code they are testing receipts. The second option is to use a different package. There is no right way, but the convention is the first one you have to choose.
  • behavior driven development focuses on the gap between specification and coding. It uses a simple DSL to convert structured human language into executable tests. The idea is to make easy the task to match between acceptance criteria and every related test related to this acceptance criteria.
  • Thank you very much for joining me in this session. If you have any question, you can find me using my nickname, fdinus. And remember, as a gophers, we have many options to make test, to create test. See you another ten by.

Transcript

This transcript was autogenerated. To make changes, submit a PR.
What if you could work with some of the world's most innovative companies, all from the comfort of a remote workplace? Andela has matched thousands of technologists across the globe to their next career adventure. We're empowering new talent worldwide, from Sao Paulo to Egypt and Lagos to Warsaw. Now the future of work is yours to create. Anytime, anywhere. The world is at your fingertips. This is Andela. Welcome to a new session of this conference. I'm glad to be part of a new version of Conf 42 Golan. This time I'm going to talk about testing using Go language. The main propose is to present some alternatives that we can have available and good practices so that testing our applications becomes a common practice in our teams. So whether you are a beginner in the go language or an experienced developer, this talk could be quite useful for you. To start, I want to introduce myself. My name is Francisco Daines, but I really prefer when the people just call me Dino. I am a principal software engineer at Walmart Chile. I'm working in the artificial intelligence and data exploitation team when we have a lot of very interesting products and most of our backend services are developed using Go language. I have experience in different languages like Java, C, Javascript and so many others. I'm using go since two years ago and I am the maintainer of Arcgo, which is a very interesting testing tool that helps teams to check if their applications implies with a set of architectural guidelines. Okay, the first part of this talk is a brief introduction to application testing. In this section I'm going to talk about why we should test our applications and talking about a few benefits of doing testing and finally presenting two approaches for planning our efforts, coding different kind of tests. So to start, we need to answer the question, why is testing important? To explain the importance of testing, I included this diagram proposed by Ken Beck. The title of this diagram is the no time for testing heard spiral. As you said, it's a very aggressive title and what it means? Well, the explanation is the more stress you feel, the less testing you will do. The less testing you do, the more errors you will make. And then the more errors you make, the more stress you feel. And this will be repeated once and once again in an infinite loop. This diagram is presented by Ken Beck in his book the Test Driven development by example. And it's a very common situation when we are doing manual testing. So to break this loop, one thing that we can do is to have automated testing as part of our practices. But why automated tests? Well, Ken Beck, in his book, in the same book tries to explain this and this is the quote. The quote says rather than apply minutes of suspecting reasoning, we can just ask the computer by making the change and running the test what it means. Well, the computer can repeat once and once again the same test battery, expecting the same results and in a very very short time. So we can make an initial effort creating this tests and then changing our application, changing over and over again and running the test and the expected results should be the application having the same behavior. So we can do this in can easy way doing automated testing. So what are the benefits of application testing? Well, here we have three. There are a lot of benefits, but those three are these most important related to application quality. The first is to reduce bugs. Why? Because if we are continuously testing different use cases for our applications, then it's more probably to find bugs and fix them before publishing our software artifacts into production environments. The second one is to improve security. Here we have to think and consider that every bug that we brings into production environment can be, can be increased the risk of a security issue. It's not always that every bug is a security issue, but we have to think and prepare and change our mindsets to trying to avoid as much as possible the bugs into production environment. And finally, testing our applications will increase the software quality because as we can catch some bugs as part of our development cycle, we are fixing them in the same development flow. Our software products will be of higher quality compared to products that have manual testing or maybe doesn't have testing at all. Those three benefits are well knowed by technical people. But how we can convince business people or stakeholders that expect we as a teams will produce different releases very closely, one by other. There are two benefits. I titled this slide the ones that really matters. But those are the ones that really matters for business people, for some kind of stakeholders. The first benefits is that testing our applications will save money. Why? Because increasing the software quality will reduce these production errors. And then if you have less errors in production, we will spend tests time in debugging and finding and fixing those issues so that we can focus our time in adding value to the business. This will save money. And the second one is even more important than the first. Doing testing in our applications will implies the customer satisfaction. Why? Because increasing deployment frequency and releasing higher quality products should improve our customer satisfaction and loyalty. So we can use these two benefits to convince business people for doing testing as part of our deployment and development. Sorry, of part of our development practices. Well, finally there are two different testing approaches. Those are the most popular approaches. The first one is the testing pyramid proposed by Mycom and the second one is the testing trophy proposed by Kensey. Dots in both approaches we have different kind of tests. The difference between these approaches is how many effort we will spend in those different kind of tests. Mycon focus the effort on have faster feedback from testing. So that's the reason why the unit test have a lot of tests. This is the representation of having more tests than service test and UI test because are more easy to code and are faster to run. So then we will have a faster feedback. On the other side. Kensidots prefers to focus our effort in integration testing because those are the harder and the more complex test and then we will testing more realistic use cases. And of course we have end to end and unit test and static ones. But there are just different approaches. You can choose whatever you prefer. There is no rule of thumb to select the right testing approach because there is no a right testing approach. And this talk is about unit testing. So it doesn't matter what kind or what approach for testing you will select. Well, the next section is testing go first steps in this section we are going to look at the testing tool provided by go language and standard package that we can use to create unit tests. So our first steps includes knowing two things. First is the go testing tool. It's a standard tool to automate the testing for desired packages. It's included as part of go language so you have nothing too extra to install in your computer. The second one is the testing package. As the go testing tool. The testing package is includes as part of these Go language, one of the standard packages. It provides features to create unit test benchmarks and fusity test. But as part of this talk, we are only used the unit test provided by this package. Okay, for more information, of course you can go to the Godoc web page for the testing package or use the command line go help test for the go testing tool. Okay, we have some minutes to create a demo to use the go testing tool and the testing package. Okay guys, we have a very simple project for the demos. It's hello world project. Here I created an examples package and in this examples package I created a very simple function. It's an add numbers function which receives a list of numbers and adds all of these numbers and return the result. To test this function, we need to create a testing file. First by conventions we try to follow a naming conventions. It's the name of the file followed by underscore test all the files that ends with underscore test will be used by go testing tool. Well then we need to create a function. The name of this function must start with test and then the name of the function that we are testing. Okay, so we are creating the test as numbers function and the parameters of these function are one variable of type testing t. This is used to tell the go testing tool that this is a simple test unit test. Okay. And then inside these test we can set the expected result and set it with a number, for example 55 and then call the function that we want to test, for example result. There we assign it to numbers and the numbers from one to that. Okay, and finally this checking, we need to check if these result is the expected value. So we can use a simple if sentence. If the expected result is different than the given result, then we can tell the tests to fail. And that is we can run the test using the id or using the terminal using the go testing tool. Go test and passing. Sorry, my mic is having troubles passing the root package and this will run all the tests under this package. Okay, in this case, the go testing tool is telling that the root package has no test files and the examples package have some test inside. Okay, well, what happens if I change the behavior of my function and for example change the return value and there is not the addition of all the numbers, it's adding all the numbers but plus one. Okay, in this case, if I run my test, the go testing tool tell me that one test fails, the test at numbers is failing. But just this, most of the time I need more information. But what happened, why the test fails. So in this case, I prefer to not use this sentence, the t fail and use t error f. With t error f, I can pass a message to the go testing tool. In this case, I compare expected value and what I got. In this case, expected result and result. So how I can see this in action? Well, we can run the test in the terminal and as you see, we are having more information about what happens. The test at numbers fails. But these go testing tool is telling me that on line eleven here, I'm raising an error with this message. I'm expecting 55, but I got 56 from TDD numbers. So I can fix this behavior and roll back this change and coming back to the right behavior of this function. Okay, so I'm going to run this again and these is all good. This is running all the tests under examples package and all have succeed. And that's all about this demo. We are creating our first test. This is a very simple test. So we come back and see another topics of this talk. So what happens if we want to test multiple cases? For example, when we want to test border cases or invalid input values or some common values for a function or unexpected values, and check for the return values that we are expecting for each case? A grown approach is to copy and paste the test, changing only the input parameters and the expected values. Should it works? Of course, but it's not the ideal. Why? Because we are duplicating code. As you can see in this example, the only thing that we are changing in these two tests are the expected value and the input parameters for the TDD numbers function. The rest of the test is exactly the same, so we need to find a way to avoid this code duplication. That's why copy and paste the test is not a good idea, but what we can do well, the proposal is called the table driven tests and the main idea is to define a table with input and expected values and these translate this into code and test for each row in these table using the same code snippet. Avoiding code duplication as you can see, in this example we are creating a table with input values and expected results and then translated this into code. Remember, tests are code so we can create use some common operations from code. In this case, we are using a for loop to iterate over the different rows in this table. So let's go and see how we can create some table driven tests in our go project. Okay, in this demo we will start with the wrong approach to test multiple cases. As you see, I am replicating the same tests and checking only these expected results for each case and the input parameters. All of the rest of the test is exactly the same. So we can see that these are almost identical. And what happened if I want to test 100 cases? Believe me, we really don't want to replicate the same test 100 times. So what are we going to do is to use table driven test to follow test table driven test we can create. First, we can create a table containing the inputs and expected values we can create using bar, for example add numbers test cases and this will be a struct and a slice of a struct. These struct continuous input values. Input values will be slice of integers and expected result. That will be an integer too. Okay, this is our struct and what values we can add here all the test cases. I will change this, right? We can create different instances to this slice. For example, the first case will have one, two and three as the input parameters, and the expected value will be six okay, for the second case we use one and 100 as the input parameters and expected value will be 101. And then we can add another value. Sorry, I'm going to copy paste this for sorry to do this more quickly. One, two and zeros should return three. Then 201 and 403 it should return 604 and then another cases. For example one, one, two to two. These four, four should return this value. And finally can empty input to return zero. Okay, what's happening here? Too many values. Don't worry about this. It's just the ide. And then we don't need to repeat the same test over and over again. We need just the TDD numbers as the base. We can test driven approach and table table driven approach and what we'll do inside this tests. Well, we need a for loop. This is the test case. We are iterate over the TDD numbers test cases and for each test case we will run a test running case and we will run a function that is the test itself describing testing t. And inside this function we will program the behavior of the test itself. It's exactly the same that we have here, but we will copy just this part and we will use the test case value. Test case input. Passing the input to the numbers we need to convert into variable arguments here. And then we don't have an expected result. This is part of the test case. Test case expected result. Oh, here we have an issue. That's a problem. Okay. These we have an expected result. And if the case is that the expected result is different than the result from the cow to the function, these will we tell the function to fail and pass it here. Test case expected result. The behavior will be exactly the same. But the big difference is that we need much less code. And it's very simple to add new test cases. We need to just add cases to this slice. Okay, how it see when we run this, we can run this here with the id and this will show us all the test cases. But maybe we can change this identification of each test case and for input. For example, we can format a message for input b and test case input. This will be more easy to identify each case. Here. As you see each case will be ident. We can identify the input parameters for each case. So this is why table driven test is a good approach. It's a good knowledge to have because will be easy some the test of different use cases. Now we are going to talk about code examples. Examples are code snippets that will be running as tests by go testing tool but also are displayed as package documentation and we can visit and see these examples in the Godoc web page for the package to create code examples we need to comply with a naming convention. Our function should start with example and have different conventions for the cases that we need to create. Examples for functions type or methods inside a type okay, here are some examples. For example to create an example for the compare function in the package string, an example for the writer type in the package buff IO and finally an example for the lines method for the scanner type which is part of the package buff IO and how is displayed this example in the Godog web page where here is an example of this. These is an example for the continuous function which is part of the string package. This is the link for looking this page and as you can see the Godoc web page will display these example and present a very simple playground that we can change some of this code and changing the output. This is very useful to create some examples and tests in line how to use a specific function. Okay, this is the time to see how we can create an example. To create an example, we need to create a function that the name of the function must start with example followed by the name of the function that we want to create. This example in this case al numbers. These functions needs no parameters so we create this block inside the block. The idea is to create an example of how we can use these function. For example, we can create a variable result that is assigned to numbers, the values one, two, four and to tell the examples to work as a test, we need to send a value or a couple of values to the standard output. In this case we can do this with the FMt package printlen result. We are sending the result value to the standard but and these assertion the check will be we need to set the output. This is reserved word for the example functions output. We expect to get the value ten in this case. So the go testing tool will run this piece of code. This part of the function will checking and will check that the output. The standard output receives the number ten and that's it. The example will be published as part of the Godoc web page of this package. Well, the next topic is test assertions. And what are assertions? Well, assertions are sentences that must be true. In any other case our test will fail. There are a lot of assertion libraries, but I prefer the testify package because it offers a lot of interesting assertions as the ones I list in this slide. For example, the equal or not equal to check if two values are equals. By the way, to check nil values or to check if a slice contains a specific value, to check kinds of errors or the message inside an error elements match. For example, allows us to check if two slice have the same values but not necessarily in the same order. And there are other interesting assertions like file system assertions, HTTP assertions and equivalence between JSON objects and general objects. What it means that two objects will be equivalent if they have the same attributes and the same values, but not necessarily in the same order. And in the left are an example of using the assert package from the testify model. And what happens if can fails. For example, in this case we are checking that the slice continuous the VAR x value and it does not contain this value. So the test will fail. And what happens these we will get a more readable output with more details. So it's an improvement to find what was wrong with our code. So let's go to hands on demo. Okay guys, for the example for the assertions package, first we need to download these testify package. This is how we can do it. Go get you to update dependencies and the URL for the package. In this case I have downloaded before so it will do nothing. But with this common you can get the testified package to create a test. To show what we can do with the assertion package, I'm going to create another test at numbers with assertions. This is another test. And to show you some features of the assert package thing. Imagine that we have two slides. Slice one that will be slice of integral having the values 1234 and five, and another slice having the same values. And we need to check that both slice continuous the same values. To do that we cannot do slice this slice one different than slice two because this cannot be doing. This operator doesn't exist on go. So first we will need to create a function that compares dot, two, slice and return if they are equals or not. But the searching package contains a function that will help this. We can call it with equals equal and put a message we need to pass the test. Slice one and slice two and a message when the two slice are different, slices are different. Okay, so to check this we will run the test and the test will pass because both contains the same values in the same order. If we create different slices, this test will fail and we will get a very detailed message about what was happening, okay, with our message here, of course, that's good. It's a very simple way to check if two slice are equals. But sometimes we need to check if two slices contains the same values, but maybe in different positions. For example, we can change the second slice, these numbers of the third, and the last number of the second slice. And if we run this, the test will fail because the equal checks that both slices contain the same numbers in the same order. To resolve this issue, assert library offers another function that is elements match. And this function check is if the elements of two slice or two objects are the same and doesn't matter, what's the position of each element. So if we run this test, should we pass? Okay, that's good. And that's a very interesting function. Another testing function is to assert that a slice contains another specific value. For example, slice one. You need to check if slice one contains the value these we will, but just a new if it doesn't contain. So we will run the test and both assertions will pass. Okay, but if we try to check if it continuous the number 35, it will fail. It fails because the slice does not contain the number that we are expecting. Well, as I said, the testify assert package is not the only option that we have. There are many others libraries like Gomega, assertions, fluent asserts and so on. If you want to search for more options, you can use the Godoc web page. Here is the search query and you can find and look for different options and choose the options that fits better for your needs. Let's talk about test doubles, especially about the mocks and staffs. This is the context that I'm going to use to trying to explain the idea behind test doubles. In the top diagram we can see a real implementation we have. But the system under test, these system that we want to test, our application for example, and this system depends on an artifacts has a dependency. This dependency can be a database, an API, a file system, a message queue or whatever. So this is a very hard to test situation because we depends on an external system we cannot trust, on having the same responses when we create queries for the database, for example, or having the same responses when we request an API. Because those systems are externals, we have no control over the content of these systems. So the idea behind the testing with doubles is that the test suite will call the system under test. As we see in the previous examples, we are calling the functions of our system. But the test suite has to create a double, which will be something like a replacement of this dependency, and the test suite can control this double, this replacement. So the system under test will think that this double is the real dependency and we can test these behavior in a controlled environment because the test suite can control what answers, what will be the responses of this double implementation. What are the benefits of this approach? Will be easier to test the system under test. We can trust in expected states because the test suite is defined the state of the double. There will be no side effects we can create, we can delete, we can run the test suite 100 times and there will be no side effects on these real systems. And finally the test will run faster because all of the dependency will be controlled inside the test suite. There are a couple of things that is important to talk about. The test doubles. The first one is that test doubles are a replacement for some dependency to EC testing. How test double works? Well, the test double merely has to provide the same API as the real dependency so that our real system, our system under test think it's the real one. We are creating a replacement and as we offer the same API the system under test think that is interacting with these real dependency. Well we have five variations for test doubles. These I will talking about very resume for each variation. The first one is the dummy object. The focus of this dummy object is just to comply with a function signature. We are not using the content of the object. We are just creating a dummy for comply with these signature and can allow us to use a function and call a function. The second one is a fake. It's a real implementation of the dependency but it's more simpler. Usually it takes some shortcut which makes them not suitable for production. For example, common example is an in memory test database. In this case we can create an in memory test database. We can control what objects or how to populate this database, but it's not ready for production. We cannot publish the application with an in memory database because we have another system most robust database as a real dependency. The third one is a stab. It's one of the important doubles that we will focus on this talk. A step provides canned answers to calls made during the test. Usually not responding at all to anything outside what's programmed in for the test, what it means we can set fixed response for calls to several functions. The next one is spice. In this case are different from stabs. In terms of that spies are focused on how we are calling the functions. We are focusing on checking what parameters we are passing in different calls and that's the idea with spice. An example can be a login service. For example. In this case we can check what messages are logged or what kind of login level we are using in different calls. This is an example of spice and the last one are mocks. They are similar to steps but the difference is that we can create different behaviors of the function based on the input parameters. But how we can use stabs. The easiest way to stop a dependency is to implement an object that complies with a required interface. This will be easy if we are following clean code principles. What it means, for example, when we are describing interface to interact with infrastructure components. The example in the left is an example of a poor design because my business type which can be can use case for example, depends directly on a concrete type, for example in an SQL driver component. In this case it's really hard to test because this concrete type will call external services and trigger side effects and this will be really hard to check and trust in responses from this interaction. The case in the right is a better design example. In these case, we are defining an interface, my interface, which defines the signature or the API that all the infrastructure component must comply with. In this case, for example, this SQL driver should comply with this interface and the business type, the my business type. Remember the use case interacts directly with the interface, not the concrete type. The my business type will not worry about what specific implementation of the my interface is receiving and is interacting with. He only worry about that these variable he's receiving must comply with the interface that is important for these my business type. Okay, in this case it's easy to stuff this behavior because in the test we can create a concrete type, a concrete struct that complies with the my interface signature. If we create this type, we can create an instance of this type, a variable of this type and pass this variable to the my business type. And with this we can staff the behavior and trust in the responses that my interface will return to. These my business type maybe these is not easy to understand when I am talking about. Let's go and see how steps works in the code in a demo. Okay, well the next example is about steps. And for these case, imagine that we have a concrete dependency. Our software depends on specific system and we have here a structure and a method. The method is called do stuff. And when this method is called we are going to print to the standard output a message, fixed message and maybe we can call external services, maybe an API or do some queries to a database or change some values in the file system, anything. Okay, this is a concrete implementation of one of our dependencies. Now for the case of a use case structure. This is an example of a use case that has a poor design because we have a dependency here, direct dependency to the concrete type. Okay, this is very simple to program we will just call the do stuff method for the concrete dependency, maybe do more things and in the main method I can call first I need to create a variable of type my use case and then call this method some business logic method and this will call my concrete dependencies do stuff method. We can test this running this main function and here you can see that my concrete dependency do stuff was called okay, but this design is very difficult, it's hard to test because we have a direct dependency and we cannot control the behavior of this dependency. So an alternative way is to create an interface. The interface will define all the API for every concrete dependency that our system needs. Okay, in this case I have defined only the do stuff method and then we can define our use case. These is other use case in terms of the interface, not in terms of the direct dependency. With this we can link the sum business logic method with the interface do stuff method. Here my other use case doesn't for this use case doesn't matter what concrete type of dependency we are working with. This use case just need a concrete type that complies with the signature of my dependency interface. And finally we can link our concrete type with the interface and assign to this inner dependency. So in the main method we can call this as use case two equals concrete stuffs new and we need to pass a dependency heard we can pass staffs my concrete dependencies. So if we call my use case two some business logic, this should run my concrete dependency do stuff method. Again here we can see that this message is repeated twice because we are calling these my concrete dependency in use case one and in use case two. So why we need to refactor our code using this interface? Because when we create a test for examples we can create this use case test file and a function test my other use case some business logic method here we need to call the first we need to create a use case. So my use case new my other use case we need to pass a concrete implementation of my dependency interface so we can create here type my staff dependency is a struct and we can create a function my staff dependency to staff sorry. And here we can another behavior we are changing this is another implementation of the my dependency interface complies with the signature so we can pass an instance of this my stuff sorry, stuff dependency dependency yeah, sorry. But for the typo here to the constructor of this use case and these we can call use case some business method and finally some searches. But we are not to do any assertion for these example we just want to know how to create a stack. So let's go and run this case. And as you see here, we are running the sum business logic method of the use case. But the do stuff method is from my stuff dependency, so I can control the behavior of the dependency. And then what are the expected values that the use case will get. So it's more easy to test some scenarios. Okay, how we can use mocks? Well, remember a mock is very similar to a stack, but the tests will also verify that the object under test calls these mock. As expected, this is represented in the diagram. The system under test will call these market object. The behavior of the market object is defined by the test suite. Because remember these market object is a w. And what things we can do with a market object, we can define several behaviors given different input parameters. And another interesting thing is that the test will fail if these system under test calls the mocket object with a set of parameters that were not configured. This is a very powerful feature. Well, in this case I will use testify as the checking library. And why? Because testify offers some useful features. For example, we can control that given several parameters, I want to return some values once, twice, definite number of times, and other interesting features of this library. This is an example of how to mock object will behave. In this case, the mocket service will return hello only once, only the first time these system under test calls this service. All the next times that the system under test call the mocket service, the my method will return by. This is the kind of things that we can do with mocked feature of testify. But as we are developers, maybe with an example, with a demo, it's more easy to understand what we can do with mock. And in this case with testify, I prepare another example and a little bit more complex example of dependency. Now our dependency interface define one method, but this method receives parameters and has a return value. Okay, so our concrete dependency to comply with this signature, so it receives an input parameter and return ensuring value. Inside this method we could call external services, external services and maybe return a response from this call. Okay, for these demo we just return three times the input value. It's a very dummy return value, but we are not trying to demonstrate anything in this concrete dependency. So we have this dependency interface, the use case receives an inner dependency using this constructor here it's very similar to the requirements for the staff doubles and our sum business logic method receive an input value and do some stuff using our dependency and return the processor value. Okay, so what can do in the tests of these methods to mock this dependency. Well, to mock the dependencies we need to first create an object. These object needs to be can instructor and implements the mock interface from the mock packet. And we need to make these mocked objects to comply with my dependencies interface. So we need to add the do stuff method that needs to receive one parameter of type string and return some string value. And this is needed to call the original method with this parameter and return the first returned value to the color. And how we can mock this object in our test? Well, first we need to create the test, then create a mocket object from the mocket object structure. And then it's the interesting part, we can link a set of arguments input parameters with a set of expected values. In this case we are telling the mock package to change the behavior of our do stuff method. This do stuff method. So when the color passes the hello as the input parameter, then the do stuff method will return a fixed value hello world. Okay, it's very simple and it's very easy to understand these logic. So then we can create a use case passing this object market object to the use case and call some business logic method with these hello as parameter and storing the message, the returned message as the message one variable and finally made an assertion that we are expecting hello world. This is the value for the variable message one. Let's go and see what happened when we run this test. Okay, this test is passed because we are expecting this value. If we change the expected value, for example by this will fail of course because we are linked the hello input parameter with hello world return value. So this will not be true. Another interesting thing is world is when we pass another value unexpected input bad. What will happen if we pass a value that was not settled up here as a linked in the mocket object? Well then the test will fail and the message will tell us that there is an expected method called because we only are expecting the hello as input parameters. We can create another input parameters and put here unexpected return value. For example, running this test we will get another error because we are trying to compare this message that will be unexpected return value with hello world. So we can fix it with for example here with hello and message two with the expected value. These test will pass because we are calling some business logic method and then the duostaff method of our mocket object with the expected parameters. At this time we are talking about how to create tests, how to use assertions and how to use test doubles to test some hard scenarios, more complex scenarios. But there is another thing that is very useful for us and it's very important to have in mind always is where to put the test, where we should create the test. So where should we put our test? There is a convention in go. Go is a language with a lot of conventions. The go testing files should always be located in the same folder and package where the code they are testing receipts. What it means for example, if I have my package with a couple of functions and I want to test this function, my testing code should receive in the same directory and use the same package, my package. In this case, the go compiler will exclude all the testing files when building our applications. So we don't need to worry about having testing code into production artifacts. Okay, we should not worry about this. But there is another interesting thing related to the package that we will use in the test. Imagine for example that we have my package, a package called my package. In this package, we have three functions. The first one is a public function, it's my public function and we have two private functions. Okay? And my test were located in these mypackage underscore tests go. So we can choose two options for these package of my test. Even if the conventions tell me that I should use my package as the package of my test, I have two options. The first one is use my package. And in this case I can test the these functions separately because as the test code is in these same package that the working code, the system under test, I can access all of these three functions. I really don't prefer this case. I prefer the second option. The second option is to use a different package. In this case can official convention is use the package underscore tests. In this case, my tests can only access my public function because as the test code is in another package, the test code cannot access the private functions of the package. There is no right way. I prefer the second one, but the convention is the first one you have to choose. I really recommend the second choice because we will testing the interfaces of the packages. So to see this in code, let's go and see some examples in a hands on demo. For the next example, imagine that we have a public function which depends on two private functions in the same package, okay? In this case they do some complex operations should do more complex logic, maybe applying a hashing algorithm or anything. And the complies with custom verification function can verify if the input value complies with a custom algorithm, for example. So our public blick functions, the function that will be visible to the rest of our application depends on these two private functions. If I want to tests this package? I can create the do stuff underscore test function. By default, by the convention my test will be created in the package, in the location package, in the same package, and I will create the function test my public function. And here can be searches. For example, I don't know return response return value equals to my public function, passing some value I don't know and then assertions and expected will be bar and can assertion will be assert equal expected return value. This is my test. Okay, but what's the issue with but my tests in the same package than the code? The issue is that I can access the private functions. For example, these do some complex operation, I can do that bar one equals two. The test will not fail because I can see these functions because I am located, my test is in the same package than the code. So the recommendation here is to change this package and use the underscore, underscore test. And in this case I need to import the location packets to access my public function. But if I try to access the private function, I cannot access this function because it's private. So this is the case to demonstrate what happened if I put my tests in one package or in another. These test can be located in the same directory but in different package using this underscore tests at the end of the testing package. So the recommendation is to use this package for the test to test the public functions and only in the cases that we need to have an exhaustive testing of. Some of our private packages use the same package of the code. Okay, this is the recommendation. Prefer use another package to test the public functions. Okay, I want to finish my talk with a brief introduction to behavior driven development because it's a topic that I think it's very useful for us and very interesting too. What's the motivation behind behavior driven development? It's to focus on the gap between specification and coding. What it means when we specify our software, we are talking about user stories, acceptance criteria, and those artifacts commonly are defined in terms of human language, maybe sometimes using Gerkin syntax. This group of, and you know, and for the other side, we have a test suite that, it's a group of many tests, but those tests are commonly created based on plain code language. So it, it's hard to match a couple of acceptance criteria with a couple of tests. It's not easy. So the idea is to have a way to make easy these match between acceptance criteria and test. And then behavior driven development uses a simple DSL, a domain specific language to convert structured human language into executable tests. Okay, so the idea is to make easy the task to match between acceptance criteria and every related test related to this acceptance criteria. Well, there are many testing frameworks that offers behavior driven development style. I prefer Ginkgo, but there are others like Gocon, B. Goblin, Mao, and Sen. So you can choose the one that fits better for your requirements. So an example for why we can, or how we can use ginkgo in a behavior driven development scenario. Imagine that we have a user story, a very simple user story. These is an example. As a dean, I want to know if a student is eligible for a scholarship so that I'll have enough information to prepare the scholarships budget. It's very simple. This user story defines four acceptance criteria. Given a student that is part of the medicine program, and these GPA is over 80. These it's eligible for a scholarship. And there are another cases. Okay, so why I prefer Ginkgo and Gomera? Ginkgo first offers a BDD style testing framework, but offers a lot of functions like describe context, gwen it and many others that allows us to create tests based on this acceptance criteria with little effort and are very human readable too. Okay, and comega. Comega is a mature assertion library. Yeah, it's very similar to the assert package of testify. But this API helps us to represent expectations in this acceptance criteria. So the combination on Ginkgo and Gomega allows us to create tests that are very close to the language user in the acceptance criteria. So let's go and see an example of this. This is an example for the first acceptance criteria. And here we are representing the checking scholarship eligibility. When the program is medicine, we create a context that is the and operation of the acceptance criteria. And these GPA is over 80, then the then will be the it in Ginkgo receives the scholarship. And inside the it that is the test itself, we can include the expectations from the Gomela. So as you see, it's very, very easy to read this test and match that. Those two tests are part of the first acceptance criteria. So this is the idea behind behavior driven development, to make easy these match between acceptance criteria and tests. Well, these funk test section. It's a requirement to connect the ginkgo and Gomega to the go testing tool. So it's a function that we always need to add. But the really important part is the describe section that we are creating here, the acceptance criteria that will be executed as test in our test suite. So this is all. I share a couple of links and reference the first two are very interesting. The first is the test driven development by example book by Ken Peck. It's a very important, it's a must to read a book for testing purposes. And the second one is a very interesting page x unit patterns. So. So with these two assets, I think that we can start with testing practices. There are links for testify, Ginkgo and Gomega. And that's all. Thank you very much for joining me in this session. If you have any question, you can find me using my nickname, fdinus, or send me an email to fdinus@gmail.com. And remember, as a gophers, we have many options to make test, to create test. So just go and put this in practice. See you another ten by.
...

Francisco Daines

Principal Software Engineer @ Walmart

Francisco Daines's LinkedIn account Francisco Daines's twitter account



Awesome tech events for

Priority access to all content

Video hallway track

Community chat

Exclusive promotions and giveaways