The scaffolding for the Go Dojo we'll be hosting at the A2Go Meetup on 10/2/2019
We will be building a CLI tool to fetch current and forecasted weather and print to Standard Output (the screen). You might notice in forecast.go
that we have the shell of a file. We've implemented the basics for running the code. Only problem: none of the actual behavior has been developed yet.
This is up to you. We've even written the tests for you! Consider them to be the specs for your functions. Start with the first test and get it to pass.
Test your implementation by running go test
from the command line.
Once you've gotten the first test passing, go to the next test.
IMPORTANT: you must delete the t.Skip("")
lines when you are ready to start working on a new test. We didn't want you to start with ALL the tests yelling at you.
We've structured the hints to help you along the way. The last hint in a function will give you the code to make the test pass. Try to do it without the hints, but don't feel bad if you need to use them. We're all learning!
go run forecast.go -forecast
What else can you do to make this tool more useful? Some ideas:
- Better error handling and reporting
- Use environment variables or CLI input for some hard-coded values (token, latitude, longitude, etc)
- Display more information to the user (emoji?)
- Use the existing benchmark and find the fastest string concatenation method
- Write more integration tests (but keep them separate from unit tests)
- Install golint
go get -v -u golang.org/x/lint/golint
, rungolint
and fix all errors
If you complete any of the stretch goals, be sure to add tests!!!
go test -coverprofile=coverage.out
go tool cover -html=coverage.out
go tool cover -func=coverage.out
You can build the executable and run a simple integration test if you do:
go build
PATH=.:$PATH go test -tags=integration
It is important to keep slow and possibly destructive integration tests separated from unit tests.
The tag used in the build comment cannot have a dash, although underscores are allowed. For example, // +build unit-tests
will not work, whereas // +build unit_tests
will.
If the test suite contains benchmarks, you can run these with the --bench
and --benchmem
flags:
go test -v --bench . --benchmem
Keep in mind that each reviewer will run benchmarks on a different machine, with different specs, so the results from these benchmark tests may vary.
- make sure to always run
go fmt
. - run golint and fix those issues
- you should always check errors, but in the interest of time, this doesn't require you to
- assertion helpers aren't needed the majority of the time and your code will look more uniform with the community if using the testing package directly
- a lot of the top level functions like GetBody don't really make sense in a "forecast" package. you should make an HTTP Client type with methods to get a forecast and such. i would take a look at https://godoc.org/github.com/google/go-github/github#Client as an example of a well-done large HTTP client
- the integration test is probably best done as a shell script in CI or similar. in general you want your package main to be as small as possible and to just wire up dependencies, so you don't need a lot of integration tests like this
- loggers should always be passed as a dependency so that a random library function doesn't start outputting logs in a binary
One way to do this last thing is make the functions take a *log.Logger, or make the functions be Methods on a struct that contains a Logger pointer:
type logger interface {
Printf(string, ...interface{})
}
type T struct {
logger
// other fields
}
Now, the consumer of type T supplies a value of type log.Logger when constructing new T‘s, and the methods on T use the logger they were provided when they want to log.
In order to use the Dark Sky API, you first need your own API key. Getting an API key is quick and free.
- https://darksky.net/dev
- Click “Try for Free”.
- Register an account and click the link sent to you in a validation email to activate your account
- Sign in
- You get 1,000 API calls per day with your free Dark Sky Developer account. There is no credit card required unless you want to upgrade to an account that will allow you more than 1,000 API calls per day.
Your secret Dark Sky API key will look something like this: 0123456789abcdef9876543210fedcba. Save it.
💡 NOTE: You should store the token securely, just as for any password. |
A primary use case for API tokens is to allow scripts to access REST APIs for applications using HTTP. Often the tokens will be sent as headers or as part of the URL.
For example, when using curl, you could do something like this:
curl -v -L \
https://api.darksky.net/forecast/32772f4b37c5a08eb4488a2ce79155bd/37.8267,-122.4233
💡 NOTE: this API keys is intended to be replaced with one from your own account. |
Look at all of that glorious weather data!
You can also make an API call to Dark Sky by typing in a URL into your browser in the following format:
https://api.darksky.net/forecast/[key]/[latitude],[longitude]
for example, to get the weather in Boston, first get the latitude and longitude coordinates (for example from Google Maps):
Let's look at what is returned (you will have to place your Dark Sky API key in the above link):
- Prefer table driven tests
- Go Walkthrough: encoding/json package
- Lesser known features of go test
- Unit Testing HTTP Client in Go
- Peter Bourgon Best Practices
- How I write Go after 7 years
- Standard Package Layout
- Structuring Applications in Go
- Dave Cheney's High Performance Go Workshop Content
- Go database/sql tutorial
- How to work with Postgres in Go
Go offers struct tags. Tags are the backticked strings you sometimes see at the end of structs, which are discoverable via reflection. These enjoy a wide range of use in the standard library in the JSON/XML and other encoding packages.
type User struct {
Name string `json:"name"`
Age int `json:"age,omitempty"`
Zipcode int `json:"zipcode,string"`
}
The json struct tag options include:
- Renaming the field’s key. A lot of JSON keys are camel cased so it can be important to change the name to match.
- The omitempty flag can be set which will remove any non-struct fields which have an empty value.
- The string flag can be used to force a field to encode as a string. For example, forcing an int field to be encoded as a quoted string.
The community welcomed struct tags and has built ORMs, further encodings, flag parsers and much more around them since, especially for these tasks, single-sourcing is beneficial for data structures.