Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/meleu/learning-golang

Learning Go with tests, from https://quii.gitbook.io/learn-go-with-tests
https://github.com/meleu/learning-golang

Last synced: 2 days ago
JSON representation

Learning Go with tests, from https://quii.gitbook.io/learn-go-with-tests

Awesome Lists containing this project

README

        

# Learning Golang

Following the [Learn Go with tests](https://quii.gitbook.io/learn-go-with-tests/) gitbook.

Here I list some things I learned in each exercise.

## Hello, World

-

### What I learned

#### Golang tooling

Start a new project as a module with:

```bash
mkdir hello
cd hello
go mod init hello
# check if the "go.mod" file was created
```

Recommended linter: [GolangCI-Lint](https://golangci-lint.run/). It can be installed with Homebrew or `asdf` (I used `asdf`).

Useful config to be used in VSCode:

```json
{
"go.lintTool": "golangci-lint",
"go.lintFlags": ["--fast"],
"go.coverOnSave": true,
"go.coverageDecorator": {
"type": "gutter",
"coveredHighlightColor": "rgba(64,128,128,0.5)",
"uncoveredHighlightColor": "rgba(128,64,64,0.25)",
"coveredGutterStyle": "blockgreen",
"uncoveredGutterStyle": "slashred"
}
}
```

Awesome tool to rerun tests on file change: [watchexec](https://github.com/watchexec/watchexec). Example of usage:

```bash
# run 'go test' when a change happens on a file ending with .go
watchexec -e go -- go test -v
```

Local documentation:

```bash
# installing local documentation
go install golang.org/x/tools/cmd/godoc@latest

# NOTE: sometimes godoc won't be in your path.
# in my case it went to `~/.asdf/` structure and I created an alias:
alias godoc=$(find $HOME/.asdf/installs/golang -type f -path '*packages/bin/godoc')

# now you can launch the local documentation server
godoc -http :8000
```

#### Golang basics

- a program have a `main` package defined with a `main` func inside.
- `func` defines a function with a name and a body (aka block)
- blocks are defined with `{`curly braces`}`
- `import "fmt"` is necessary to use `fmt.Println`
- `if` works like other programming languages, without `(`parenthesis`)`
- variables are ~~assigned~~ declared like this: `varName := value`
- I [researched](https://stackoverflow.com/a/36513229/6354514) and realized that
- `:=` for [short variable declarations](https://go.dev/ref/spec#Short_variable_declarations) (with type inference)
- `=` for [variable declarations](https://go.dev/ref/spec#Variable_declarations) and [assignments](https://go.dev/ref/spec#Assignment_statements).
- constants are defined like `const myConst = "My String"`
- `PublicFunctions` start with a capital letter and `privateFunctions` start with a lowercase.
- `func greetingPrefix(language string) (prefix string)` creates a **named return value**
- creates a variable called `prefix` in the function
- it will be assigned the "zero" value. In this case (`string`): `""`
- example (also showing a `switch` statement):

```go
func greetingPrefix(language string) (prefix string) {
switch language {
case spanish:
prefix = spanishHelloPrefix
case french:
prefix = frenchHelloPrefix
case portuguese:
prefix = portugueseHelloPrefix
default:
prefix = englishHelloPrefix
}
return
}
```

- example of grouping constants:

```go
const (
spanish = "Spanish"
french = "French"
portuguese = "Portuguese"

englishHelloPrefix = "Hello, "
spanishHelloPrefix = "Hola, "
frenchHelloPrefix = "Bonjour, "
portugueseHelloPrefix = "Olรก, "
)
```

#### Golang testing

- file name must be `${something}_test.go`
- `import "testing"`
- the test function must start with `Test`
- test function takes only one argument `t *testing.T` (it's your "hook" into the testing framework)
- `t.Errorf` prints a message when a test fails.
- `%q` means "string surrounded with double quotes", in the string format context
- subtests go in `t.Run("test name", testFunction)`. Example:

```go
func TestHello(t *testing.T) {
// ๐Ÿ‘‡ t.Run(testName, testFunction)
t.Run("say hello to people", func(t *testing.T) {
actual := Hello("Chris")
expected := "Hello, Chris!"
assertCorrectMessage(t, actual, expected)
})

// ๐Ÿ‘‡ t.Run(testName, testFunction)
t.Run("say 'Hello, World!' when passing empty string", func(t *testing.T) {
actual := Hello("")
expected := "Hello, World!"
assertCorrectMessage(t, actual, expected)
})
}

// comments about this helper function right after this codeblock
func assertCorrectMessage(t testing.TB, actual, expected string) {
t.Helper() // <-- pra que isso?
if actual != expected {
t.Errorf("expected: %q; actual: %q", expected, actual)
}
}
```

- For helper functions, accept `testing.TB` is a good idea.
- `t.Helper` is needed to report the caller line number when the test fails
(not the line number in the helper function)

## Integers

### Testable Examples

[Official article](https://go.dev/blog/examples).

Here's an example:

```go
func ExampleAdd() {
sum := Add(1, 5)
fmt.Println(sum)
// Output: 6
}
```

The special comment `// Output: 6` makes the example to be executed.

This example also goes to the documentation of your package. You can check by
running `godoc -http :8000` and looking for the `Integers` package.

## Iteration

### Golang

In Go you iterate using `for`. There are **no** `while`, `do`, `until` keywords.

It's usually used like other C-like languages:

```go
for i := 0; i < 5; i++ {
repeated += character
}
```

Other ways of using `for` are listed here:

### Benchmarking

Example:

```go
func BenchmarkRepeat(b *testing.B) {
for i := 0; i < b.N; i++ {
Repeat("a")
}
}
```

- `testing.B` gives you access to the (cryptic) `b.N`.
- the benchmark code is executed `b.N` times and measures how long it takes.
- the amount of times shouldn't matter, the framework determine what is a "good" value.
- run the benchmark with `go test -bench=.`
- the results show how many times the code was executed and how many nanoseconds it took to run.

## Arrays

### Golang

Arrays can be initialized in two ways:

- `[N]type{value1, value2, ..., valueN}`
- example: `numbers := [5]int{1, 2, 3, 4, 5}`
- `[...]type{value1, value2, ..., valueN}`
- example: `numbers := [...]int{1, 2, 3, 4, 5}`

The `%v` placeholder print the variable in the "default" format (in this case
an array).

Let's check the `range` instruction:

```go
func Sum(numbers [5]int) int {
sum := 0
// numbers is the array given as argument
for _, number := range numbers {
sum += number
}
return sum
}
```

- `range` let you iterate over an array
- on each iteration it returns two values: the index and the value
- in the example we are choosing to ignore the index by using the
`_` [blank identifier](https://go.dev/doc/effective_go#blank)

## Slices

### Golang

The [slice type](https://go.dev/doc/effective_go#slices) allows us to have
collections of any size. The syntax is very similar to arrays, just omit
the size.

Example: `mySlice := []int{1, 2, 3}`

Checking equality of slices:

```go
import "reflect"

reflect.DeepEqual(slice1, slice2)
```

Adding elements to a slice:

```go
// append() creates a new slice, therefore you need to assign the variable again
mySlice = append(mySlice, newElement)

// you can use append() to merge two slices:
mySlice = append(mySlice, anotherSlice...)
```

### Golang testing

Check the coverage with

```bash
go test -cover
```

## Structs, Methods and Interfaces

### Golang

```go
// declaring a struct
type Rectangle struct {
Width float64
Height float64
}

// declaring a method for a struct
func (r Rectangle) Area() float64 {
return r.Height * r.Width
}
```

An interesting thing about interfaces in Go (which makes me remember Duck Typing):
**interface resolution is implicit**.

Here's an example of an interface:

```go
type Shape interface {
Area() float64
}
```

Once this ๐Ÿ‘† is declared **any** struct witha method called `Area()` that returns
a `float64` is automatically considered a `Shape`.

We don't need to explicitly say "My type Foo implements interface Bar".

### Golang testing

I learned about Table Driven Tests. It's useful but not that easy to read
(without some training).

Here's an example:

```go
func TestArea(t *testing.T) {
// using Table Drive Tests
areaTests := []struct {
name string
shape Shape
hasArea float64
}{
{
name: "Rectangle",
shape: Rectangle{Width: 12, Height: 6},
hasArea: 72.0,
},
{
name: "Circle",
shape: Circle{Radius: 10},
hasArea: 314.1592653589793,
},
{
name: "Triangle",
shape: Triangle{Base: 12, Height: 6},
hasArea: 36.0,
},
}

for _, tt := range areaTests {
// using tt.name to use it as the `t.Run` test name
t.Run(tt.name, func(t *testing.T) {
got := tt.shape.Area()
if got != tt.hasArea {
// the `%#v` format string prints the struct with values in its fields
t.Errorf("%#v got %g; want %g", tt.shape, got, tt.hasArea)
}
})
}
}
```