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.