My attempt to learn Go via testing
- A chapter a day
- A continuous integration in place via GitHub actions to visually see green ticks for fun
- Could include an additional
testable example
in collection of test. In this instance, running the commandgo test -v
would example the example and will execute the unit test. Remember to include// Output
in the unit test method so that the unit test runs. If it is not, the test will not run.
- Learned to use benchmark testing. Useful for testing performance of new code with the following command
go test -bench=.
- Use
go test -cover
to display test coverage if needed. Great way to determining areas of code that need testing for. - The use of
make
,slices
,array
,append
- Implicit interfaces and the use of table driven tests to build a list of test cases (anonymous structs are use in the table of driven tests too)
- Note the ability to run single test case instead of everything with the following command
go test -run TestArea/Rectangle
- A field that starts with a lowercase symbol is private to outside the package it is defined in.
- In a function or a method in Go, the arguments are copied. It is important to use pointers so we ensure we take the pointer of a type that has already been instantiated to update/modify rather than update/modify a copy of it when the method is called (the use of a
*
) - Methods within structs perform automatic de-referencing of the pointers. Whereas, in a normal function you would need to. Note the differences and instances when using
&
(gets the memory address for this variable currently in the code) and*
(before a variable is for de-referencing and after atype
we're saying it's a pointer for thistype
) - Ability to create new types from existing ones. Example is
type Ringgit int
withRinggit
being the new type name - Stringer Interface already defined in fmt package and essentially is called when printing using the %s format. Allows us to customize how our type should be printed
(t testing.TB).Fatal
stops test and prevents more unnecessary asserts from being performedvar
keyword allows us to define values global to the packageerrcheck
a Golang linter helps with identifying missing code blocks that do not check forerror
being returned
- Maps can return 2 values - the value and whether key exists in map
- Maps can be
nil
. Ensure to never initialize an empty map variable like sovar m map[string]string
- Do not need to pass
map
type as an address like usual with*type
as Go makes a copy of just the pointer part already for us but not the underlying data structure that contains the data. Maps are pointers to runtime types - Create a specific
error type
by ensuring type implements theerror
interface with the implementation of theError()
method
- Learnt that
httpResponseWriter
implementsio.Writer
which is a clear indication of how handlers can be tested with the use of dependency injection and testing the output of the handlers via its response writers
- A great example of mocking. This allow us inject a mock dependency for testing purposes and also still ensure code work as intended in prod (specific to time in this instance). This is important as it promotes loose coupling and allow us to test our dependency
- Be aware that an existing spy object can be further extended to implement multiple interfaces as long as it implements the method signature listed in the interface. This allow us to test multiple methods in this scenario
- Note when using concurrency, "not enough time to add their results", when for looping each variable; important to ensure that a new variable is use for each iteration (each goroutine spawned via
go func(variableNameInGoRoutine){}(variablePassedIntoGoRoutine)
if not done same variable is replaced with a new variable for new goroutine and the errorfatal error: concurrent map writes
which is due to multiplegoroutines
writing to the results map at exactly the same time (specific tomap
in Go) - If multiple things attempting to write to one thing, this is a race condition. To avoid such case, can use the command
go test -race
. - This can be resolve appropriately with the use of
channels
. Channels coordinate the goroutines which can both receive and send values. This ability allows it to communicate between different processes. Remember: think about parent process and each of thegoroutines
that it makes to do the work of running a function. - By sending results into the channel (receiver expression), we can control timing of each write into the results map, ensuring that it happens one at a time. Function calls in a goroutine and sending results into/from channel is happening concurrently inside its own process and finally each result is dealt with one at a time.
httptest
used in this chapter. Worth referring back to this for practice to test handlers or mock a server later on.select
allows you to wait for multiple channels and use whichever that returns a value first.time.After
will be useful to timeout inselect
block (since it returns achan
) to ensure that if none of the channels return anything we create a timeout instead
sync.WaitGroup
is used to synchronise concurrent processes. It waits for a collection of goroutines to finish.- The main goroutine calls
Add
to set the number of goroutines to wait for. Then each of the goroutines runs and callsDone
when finished. At the same time,Wait
can be used to block until all goroutines have finished. Mutex
can be used to ensure that something is locked which ensures that only one goroutine can modify something one at a time. Do not directly embedmutex
into struct and remember to not create a copy of mutex when passing to other functions which golang automatically does- Common mistake is to overuse
channels
andgoroutines
. Use channels when passing ownership of data and use mutexes for managing state. Usego vet
to catch subtle bugs.
- Use
request.Context
and pass that into any methods making I/O like executions for tracking purposes down the road - When creating a new context out of the incoming request (which is a common practice), remember to send context as the method's first argument and ensure that the timeout or deadline method is used so that the context is cancelled after a certain amount of time has passed.