- Create new directory outside of your
$GOPATH
called mystore
. To lookup your path execute the following in a console window:
$ go env
...
GOPATH="/Users/carlosgabaldon/go"
...
$ cd
$ mkdir mystore
$ cd mystore
- Create a Go module
$ go mod init example.com/mystore
go: creating new go.mod: module example.com/mystore
$ cat go.mod
module example.com/mystore
go 1.14
- Create new directory called
store
$ mkdir store
$ cd store
- Create a new file called
customer.go
which will manage your customer data.
$ touch customer.go
- Using your editor of choice add the following to
customer.go
:
package store
import "errors"
// List is a customer shopping list
type List map[string]float64
// Customer is a shopper at my store
type Customer struct {
Name string
ShoppingList List
}
// AddItemToShoppingList adds an item to the shopping list, returns error if it already exists
func (c *Customer) AddItemToShoppingList(item string, price float64) error {
_, exists := c.ShoppingList[item]
if exists {
return errors.New("item already exists in your list")
}
c.ShoppingList[item] = price
return nil
}
- Add a new file within the
store
directory called customer_test.go
with the following:
package store
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestAddItemToShoppingList(t *testing.T) {
assert := assert.New(t)
shopper := Customer{
Name: "John",
ShoppingList: List{
"Eggs": 2.99,
"Bread": 2.49,
},
}
err := shopper.AddItemToShoppingList("Milk", 3.99)
assert.NoError(err)
}
- Update your module with the new dependencies.
$ go mod tidy
- Run the tests
$ go test ./...
ok example.com/mystore/store 0.374s
- Add a second test to
customer_test.go
func TestAddItemToShoppingListExisting(t *testing.T) {
assert := assert.New(t)
shopper := Customer{
Name: "John",
ShoppingList: List{
"Eggs": 2.99,
"Bread": 2.49,
"Milk": 3.99,
},
}
err := shopper.AddItemToShoppingList("Milk", 3.99)
assert.NoError(err)
}
- Run the tests again (will fail)
? example.com/mystore [no test files]
--- FAIL: TestAddItemToShoppingListExisting (0.00s)
customer_test.go:35:
Error Trace: customer_test.go:35
Error: Received unexpected error:
item already exists in your list
Test: TestAddItemToShoppingListExisting
FAIL
FAIL example.com/mystore/store 0.137s
FAIL
$
- Tests failed because we are getting an expected error. Make the test pass by asserting that we expect an error.
func TestAddItemToShoppingListExisting(t *testing.T) {
assert := assert.New(t)
shopper := Customer{
Name: "John",
ShoppingList: List{
"Eggs": 2.99,
"Bread": 2.49,
"Milk": 3.99,
},
}
err := shopper.AddItemToShoppingList("Milk", 3.99)
assert.Error(err)
}
- Run the tests again (will pass now)
$ go test ./...
ok example.com/mystore/store 0.436s
- Add the following import to
customer.go
import (
//..
"net/http"
)
- Add this method to
customer.go
// List lists a customers shopping list.
func (c *Customer) List(w http.ResponseWriter, req *http.Request) {
fmt.Fprintf(w, "Welcome to my store %s\n", c.Name)
fmt.Fprintf(w, "\n%s you are buying the following items:", c.Name)
for item, price := range c.ShoppingList {
fmt.Fprintf(w, "\n%s $%.2f", item, price)
}
fmt.Fprintf(w, "\n\nThanks for shopping with us %s\n", c.Name)
}
- Create a new file called
main.go
which will be the entry point of your application.
$ touch main.go
- Add the following to
main.go
package main
import (
"net/http"
"example.com/mystore/store"
"github.com/prometheus/common/log"
)
func main() {
shopper := store.Customer{
Name: "John",
ShoppingList: store.List{
"Eggs": 2.99,
"Milk": 1.99,
"Bread": 2.49,
},
}
http.HandleFunc("/list", shopper.List)
log.Fatal(http.ListenAndServe("localhost:8000", nil))
}
- Update your module with the new dependencies.
$ go mod tidy
- From within the root of
mystore
enter the following into your console to start the web server:
$ go run main.go
- Open a web browser and navigate to http://localhost:8000/list
- The following should be displayed in the browser
Welcome to my store John
John you are buying the following items:
Bread $2.49
Eggs $2.99
Milk $1.99
Thanks for shopping with us John
- One thing to notice is that when refreshing the page the order of the list changes. This is because we used a
map
type for our ShoppingList. When iterating over a map with a range loop, the iteration order is not specified and is not guaranteed to be the same from one iteration to the next. To get a stable iteration order we must maintain a separate data structure that specifies that order. Update customer.go
with the following and then restart the server:
import (
//..
"sort"
)
// ..
func (c *Customer) List(w http.ResponseWriter, req *http.Request) {
fmt.Fprintf(w, "Welcome to my store %s\n", c.Name)
fmt.Fprintf(w, "\n%s you are buying the following items:", c.Name)
var keys []string
for k := range c.ShoppingList {
keys = append(keys, k)
}
sort.Strings(keys)
for _, k := range keys {
fmt.Fprintf(w, "\n%s $%.2f", k, c.ShoppingList[k])
}
fmt.Fprintf(w, "\n\nThanks for shopping with us %s\n", c.Name)
}
package main
import (
"net/http"
"example.com/mystore/store"
"github.com/prometheus/common/log"
)
func main() {
shopper := store.Customer{
Name: "John",
ShoppingList: store.List{
"Eggs": 2.99,
"Milk": 1.99,
"Bread": 2.49,
},
}
http.HandleFunc("/list", shopper.List)
log.Fatal(http.ListenAndServe("localhost:8000", nil))
}
package store
import (
"errors"
"fmt"
"net/http"
"sort"
)
// List is a customer shopping list
type List map[string]float64
// Customer is a shoper at my store
type Customer struct {
Name string
ShoppingList List
}
// List lists a customers shopping list.
func (c *Customer) List(w http.ResponseWriter, req *http.Request) {
fmt.Fprintf(w, "Welcome to my store %s\n", c.Name)
fmt.Fprintf(w, "\n%s you are buying the following items:", c.Name)
// Not sorted approach
// When iterating over a map with a range loop,
// the iteration order is not specified and is
// not guaranteed to be the same from one iteration
// to the next.
/* for item, price := range c.ShoppingList {
fmt.Fprintf(w, "\n%s $%.2f", item, price)
} */
// Sorted approach
// If you require a stable iteration order you must maintain
// a separate data structure that specifies that order.
var keys []string
for k := range c.ShoppingList {
keys = append(keys, k)
}
sort.Strings(keys)
for _, k := range keys {
fmt.Fprintf(w, "\n%s $%.2f", k, c.ShoppingList[k])
}
fmt.Fprintf(w, "\n\nThanks for shopping with us %s\n", c.Name)
}
// AddItemToShoppingList adds an item to the shopping list, returns error if it already exists
func (c *Customer) AddItemToShoppingList(item string, price float64) error {
_, exists := c.ShoppingList[item]
if exists {
return errors.New("item already exists in your list")
}
c.ShoppingList[item] = price
return nil
}
module example.com/mystore
go 1.14
require github.com/prometheus/common v0.9.1