Monday, May 2, 2016

Testing in Go from the Ground Up Part 1: The basics of Go testing and Testify

One great language feature of Go is that it has a built-in testing package for writing tests and the command-line tool go test for running them. This means if you want your Go code to have automated tests, you don't need to download any extra tools to run tests. Not only that, but the main testing library is very simple, so your tests are just regular Go code.

If you're learning Go, and especially if you are learning testing for the first time, this tutorial is for you. It's also for programmers who are experienced in another language, but are trying to figure out testing for the first time.


Why automated testing?

If you haven't written automated tests before, you might wonder why people do automated tests. On my early web development projects, I didn't write tests and relied solely on my eyeballs to confirm that my code worked, but as a result, I missed a lot of glaring errors. Here are some reasons why you should have automated tests in your code:

  • The obvious one: So you know your code works
  • As your projects grow, simply relying on the eyeball test takes forever and you're more likely to miss bugs in your code
  • As your projects grow, your tests can show exactly when a bug shows up in part of your code so you can fix it. This lets you detect bugs early in the development cycle!
  • Writing tests makes you think clearly about what your code's really supposed to do
  • In the Go community, your tests can serve as “the other documentation” for showing people examples of how to use your code

At Meta Search, where I work, we write a lot of automated tests for all these reasons, and our tests contribute a lot to how we can get code out on an enormous codebase very quickly and reliably. So without further ado, let's write some tests!


A full name first example

For a first example, let's say we have a Go function that takes someone's first and last name and returns their full name. Write this into fullname.go:
package testingtutorial

func FullName(firstName, lastName string) string {
    return firstName + lastName
}

This function is supposed to return a string of someone's full name, so if I pass in my name and call FullName("Andy", "Haskell"), the string of test I want back is "Andy Haskell". Functions like this are common on websites, for example when they are displaying your full name on a social media profile. But there's a bug in this function, and a test will reveal it.


Testing our function

As I mentioned, we have an example of something we'd put into the function (my first and last name) and what we expect to get back. That means we know enough to write a test for this function. Put this into fullname_test.go:
package testingtutorial

import "testing"

func TestFullName(t *testing.T) {
    expectedFullName := "Andy Haskell"
    fullNameResult := FullName("Andy", "Haskell")

    if expectedFullName != fullNameResult {
        t.Errorf("Expected full name %s, got %s", expectedFullName, fullNameResult)
    }
}

TestFullName is the test function we wrote for testing the FullName function. The value expectedFullName is what we want FullName to return when we pass in my first and last name, and the value fullNameResult is what it actually returns. In the if statement below, if those values are different, we consider the test to have failed by calling t.Errorf, and we log a message about the error in Errorf using printf format.

What's that weird t though? t is a testing.T, and it's used for keeping track of a test and telling Go that your function is a test. Any function in Go whose name starts with Test and takes in a testing.T is considered a test, and the testing.T lets you know whether the test passed or failed. (Similarly, Go knows a file contains tests if it ends with _test.go).

The rule for a Go test is if a T's Errorf, Fatalf, or Fail function is called, the test failed. If not, the test passed. 

So let's see this test in action. In the command line, run go test and you should get:
--- FAIL: TestFullName (0.00s)
 fullname_test.go:11: Expected full name Andy Haskell, got AndyHaskell
FAIL
exit status 1
FAIL github.com/AndyHaskell/testing-tutorial 0.004s

A failed test! But that's actually a good thing because that means our test unearthed a bug. Here's a play-by-play of the test:

- go test runs TestFullName. We run FullName on "Andy" and "Haskell", and get back "AndyHaskell"
- In the if statement, the value of expectedFullName is "Andy Haskell" and the value of fullNameResult is "AndyHaskell".
- Since these two values are different, t.Errorf is called, and the message “Expected full name Andy Haskell, got AndyHaskell” is logged. Since t.Errorf was called, the test failed.


Fixing the bug

Now that we found the bug, let's fix it. It's a pretty simple bug. All we need to do is add in a space between firstName and lastName in FullName:
func FullName(firstName, lastName string) string {
    return firstName + " " + lastName
}

Now with that bug fix, let's confirm that our test now works. Run go test and you should get:
PASS
ok   github.com/AndyHaskell/testing-tutorial 0.004s

A passed test! Since we now got the result we expected for fullName, t.Errorf is never called, so the TestFullName is considered to have passed!

By the way, while that was a simple bug, simple bugs like this happen all the time in big software projects, and when the bug is simple and the codebase is big, they're easy to miss. That's why it's smart to write tests!

 When the bug you spent hours fixing turned out
to be a few keystrokes to fix


Multiple assertions

The if statement of that test:
if expectedFullName != fullNameResult {
    t.Errorf("Expected full name %s, got %s", expectedFullName, fullNameResult)
}

is called an assertion, because it asserts what what we want our results to be. The test we did had one assertion, but you can have more than one in a test. Let's say we wanted to take a pair of latitude and longitude coordinates in JSON (a format popular for communicating data) into a Go struct. For example if we define the struct:
type Coordinate struct {
    Lat float64
    Lng float64
}
then the JSON {"Lat: 42.3", "Lng:-71.1"} would convert to Coordinate{Lat: 42.3, Lng: -71.1}

The conversion function would look like this:
package testingtutorial

import "encoding/json"

type Coordinate struct {
    Lat float64
    Lng float64
}

func ConvertCoordinates(coordJSON []byte) (*Coordinate, error) {
    var c Coordinate
    if err := json.Unmarshal(coordJSON, &c); err != nil {
        return nil, err
    }
    return &c, nil
}

If we were testing ConvertCoordinates, the things we'd need to test for are:
- ConvertCoordinates didn't error
- ConvertCoordinates gave us the correct latitude coordinate
- ConvertCoordinates gave us the correct longitude coordinate
- If invalid JSON is passed into ConvertCoordinates, the function DOES error

That's four assertions we'll want to make for the test. Here's what the test will look like:
func TestConvertCoordinates(t *testing.T) {
    validCoordinateJSON := []byte(`{"Lat": 42.3, "Lng": -71.1}`)
    invalidCoordinateJSON := []byte(`{ThisIsInvalidJSON`)
    coords, err := ConvertCoordinates(validCoordinateJSON)
    if err != nil {
        t.Fatalf("Error unmarshalling valid coordinates: %v", err)
    }
    if coords.Lat != 42.3 {
        t.Errorf("Expected latitude 42.3, got %f", coords.Lat)
    }
    if coords.Lng != -71.1 {
        t.Errorf("Expected longitude -71.1, got %f", coords.Lng)
    }

    coords, err = ConvertCoordinates(invalidCoordinateJSON)
    if err == nil {
        t.Errorf("Unmarshal should have errored for invalid coordinates")
    }
}

As you can see, your test can have as many assertions as you need. The first one asserts that there was no error calling ConvertCoordinates on valid coordinates and the next two assertions make sure we got the right coordinates. The last assertion asserts that ConvertCoordinates returns an error for invalid coordinates. As your codebase for a project gets bigger, your tests will get more complex as well, so they will have a lot of assertions.

By the way, note that when we check if there was an error calling ConvertCoordinates on valid coordinates, rather than logging the error with Errorf we use Fatalf. These methods both cause a test to fail, but the difference is with Errorf the test keeps going through the other assertions while with Fatalf the test stops altogether.

I chose Fatalf for that assertion because if ConvertCoordinates errors, we won't have a valid Coordinate struct to test. My general rule is if your test has an assertion where the rest of the test depends on that assertion passing, use Fatalf in the assertion. Otherwise, stick to Errorf so if your assertion fails you can see how the rest of your test goes.

Another good example of a time to use Fatalf is in a part of your test code that sets the stage for the test. If you're testing code that works with a database, your test might begin with setting up a mock database. If that fails, your test won't work since you don't have a database so you'd do something like: 
mockDatabase, err := setupMockDatabase()
if err != nil {
    t.Fatalf("Error setting up mock database: %v", err)
}
// If we get past this if statement, run the rest of the test!
to stop the test if the database setup fails.

More concise assertions with Testify

Overall the basic Go testing package is very simple and flexible so you can test your Go code using regular old Go code. However, as your number of assertions go up, you can end up with a lot of bulky three-line if statements that make your tests taller. 

Luckily, there's a series of packages called Testify in Go that let you write one-line assertions with a readable syntax, and they work with the testing.T struct to be a natural extension of the testing package, rather than a giant framework with a ton of new rules to learn. Instead of writing an assertion like this:
if coords.Lat != 42.3 {
    t.Errorf("Expected latitude 42.3, got %f", coords.Lat)
}

You can write it like this:
assert.Equal(t, 42.3, coords.Lat, "Expected latitude 42.3, got %f", coords.Lat)

To get Testify, run go get github.com/stretchr/testify/assert and go get github.com/stretchr/testify/require. Testify Assert is a package for different kinds of testing assertions. You can assert two values are equal with assert.Equal, you can assert they are NotEqual, you can assert that a value is Nil or NotNil, you can assert that a slice or map is a certain Len, and there are tons more assertions you can check out in the godoc for Tesitfy Assert!

Require is almost identical to Assert but if a Require assertion fails, the test stops like in Fatalf, while the test keeps going on a fails Assert assertion. So now our TestConvertCoordinates function would now look like:
package testingtutorial

import (
    "testing"

    "github.com/stretchr/testify/assert"
    "github.com/stretchr/testify/require"
)

func TestConvertCoordinatesTestify(t *testing.T) {
    validCoordinateJSON := []byte(`{"Lat": 42.3, "Lng": -71.1}`)
    invalidCoordinateJSON := []byte(`{ThisIsInvalidJSON`)

    coords, err := ConvertCoordinates(validCoordinateJSON)
    require.Nil(t, err, "Error unmarshalling valid coordinates: %v", err)
    assert.Equal(t, 42.3, coords.Lat, "Expected latitude 42.3, got %f", coords.Lat)
    assert.Equal(t, -71.1, coords.Lng, "Expected longitude -71.1, got %f", coords.Lng)

    coords, err = ConvertCoordinates(invalidCoordinateJSON)
    assert.NotNil(t, err, "Unmarshal should have errored for invalid coordinates")
}

Now we have a test that's much cleaner, shorter (19 lines vs. 13 lines), and easier to read! And as I mentioned earlier, that's especially important in Go testing where your automated tests double as documentation.

Note that our Fatalf test to check that ConvertCoordinates didn't error now checks for the error with require.Nil. Meanwhile we use assert.Equal to check that we got the right coordinates, and assert.NotNil to check that we get an error if we call ConvertCoordinates on invalid JSON. Technically all of those assertions could have used Equal or NotEqual, but Testify assert gives us flexibility in how we write our tests.

Another convenient feature of Testify assertions is that if an assertion fails, Testify gives us an error message showing the call stack that led up to the assertion failure so we can track bugs more easily.

Let's say we wanted to move both coordinate assertions to a helper function:
func assertCoordinates(t *testing.T, lat, lng float64, coords *Coordinate) {
    assert.Equal(t, lat, coords.Lat, "Expected latitude %f, got %f",
        lat, coords.Lat)
    assert.Equal(t, lng, coords.Lng, "Expected longitude %f, got %f",
        lng, coords.Lng)
}

And in the main test, we call these assertions (but expect the wrong coordinates):
require.Nil(t, err, "Error unmarshalling valid coordinates: %v", err)
assertCoordinates(t, 39.7, -71.1, coords)

We would get the error:
--- FAIL: TestConvertCoordinatesWithHelper (0.00s)
      Error Trace:   coords_testify_helper_test.go:11
                     coords_testify_helper_test.go:21
      Error:         Not equal: 39.7 (expected)
                             != 42.3 (actual)
      Messages:      Expected latitude 39.700000, got 42.300000

This tells us not only that the assertion failed in assertCoordinates, but also that the call to assertCoordinates was on line 21. As your tests get bigger and more complex, helper functions are incredibly useful for keeping your tests easier to read and keeping redundant code out of the way of the meat of the tests. As we have just seen, this Testify assert call stack can be extremely useful for tracking assertion failures that happen inside a helper function!

So now we've seen the basics of how to write Go tests with the built-in Go testing package and Testify Assert and Require. While it's pretty simple, these testing tools are incredibly flexible for writing tests. Since all your test code is just regular Go code, you can make your tests as simple or complex as they need to be.

In my next testing tutorial, I will be showing techniques on how to keep your tests well-managed as your project evolves and you have more code to test. For homework, if you have some Go code you've written, try writing some tests with the testing, assert, and require packages.


Stay slothful! ((.(⊙ω⊙)((.


Image credits:

- The Go Gopher in the top graphic was originally drawn by Renee French and is licensed under CC BY 3.0


Also, shoutout to everyone who helped peer review this blog post, both Gophers and non-Gophers, including my parents, Josh Romaker (soon to be a teacher), Jason Briggs and Emily Pavlini (two Meta cofounders), and Jo Chasinga, Aaron Schlesinger, and Carlisia Campos who peer reviewed my blog post through the Gophers Slack channel! The #GopherDen just keeps getting bigger!!

Thursday, March 24, 2016

Have an #EpicSemester, startup style

Are you looking to have a productive semester of college where you learn a lot, get your solo and group projects done faster, slay your finals, and still have time for socializing, eating healthy not-pizza foods, and yes, watching sloth videos? Then the startup community has the tools you need!

At Meta Search, the startup I work at, we do a lot of collaborative work, and that means we need to communicate well, stay on the same page about where everyone's at on our tasks, and keep track of all the changes in our software. For that we use some sweet productivity software to coordinate getting our code out rapidly, and I definitely could have used that software when I was at Tufts!

I never learned productivity techniques in college, so to get A's I ended up packing in lots of inefficient study hours, becoming an energy drink connoisseur, and having really stressful finals weeks. If you've been on that struggle bus, read on to turn that struggle bus into a struggle bullet train!



Got a college group project and want more convenient communication than just group texting? Wish your class had an online chat room for setting up study groups or wrapping your head around a tough part of the lecture? Wanna start a forum for your off-campus housemates? Then get your classmates on Slack!

Slack is like a much classier version of a chat room or forum, and it's easy to start a Slack team for a group you're in. If you're on a Slack team with your classmates, you can use Slack to talk about stuff you're figuring out during studying, share jokes and quotable moments in the class, and figure out where and when you and your group project partners can meet up.

At Meta, we collaborate a lot by sending direct messages to each other through Slack. So we Slack each other questions about the tasks we're on, we send snippets of the code we want to point out in our massive codebase, and we link each other to blog posts about new concepts and technologies we're interested in using in our code. Slack also lets you integrate your chat room with a lot of other sites. For example, we hooked up our GitHub repositories to our #dev channel, so if there's a change in the code, everyone hears about it! This makes it much easier for us to stay on the same page on where everyone's at.

Besides getting work done, though, we use our #food channel to announce when there's free food somewhere in the office (almost every day), our #product channel for talking about our web design, and our #general and #random channels for socializing and sharing funny pictures and videos. But you know what else is great about using Slack to communicate? CUSTOM EMOJI!

As you can see, Meta has caught 'em all, and so can your class if you have Slack!



We've all had group projects that end up with all the work piled on right before the due date, or slowed down because not everyone's on the same page or because it's hard to find a time to meet. But it's easy to see what needs to get done and divide it up, if you write it down on a Trello board!

At Meta, we use Trello boards primarily to organize a scrum workflow; we break up the work into 2-week sprints, writing a card on the board for each task, and then assigning them to different members of the team. During a sprint, we have a list of the cards for what needs to get done, which we drag from the backlog to the in progress list when we start it and the done pile when we finish.

For an example of a Trello board, here's a board of me and my dog Lola writing one of my programming tutorials.


Another great feature of Trello is that on each card you can make checklists and check things off as you go, so if you're not sure where to get started on a card, you can break it down into a checklist to figure out where to start, useful for both solo and group projects, or for figuring out what things you need to study before an exam and when you need to go to office hours. Not knowing what needs to get done is the worst for studying and can run up your late night pizza delivery bills, so use Trello to save time on figuring that out!

Here's an example of a Trello checklist on one of my dog Lola's Trello cards from when I was writing my last blog post.


Most projects in college are only about the length of one or two scrum sprints, but the short length makes planning and staying on the same page critical. The best group projects I had when I was at Tufts were the ones where we were required to plan the project before getting cracking, and for that, Trello's got your back!

By the way, Lola really did learn how to toodle-oo on that checklist!




Ever have a project you step away from for a while only to come back to it later and have no idea where you saved your files? Or you ever have a syllabus with a non-descript filename get buried in your Documents folder… or was it your Google Drive account? I've been there, done that, and digging through all my folders was the worst, especially if it's a file with a long loading time. Luckily, Meta makes finding those files easier than ever to find by tagging them, making finding them as easy as typing into a search bar!




You can use Meta to search the files on your hard drive if you're using it on a Mac, but besides searching on your own computer, you can integrate your Meta account with other sites where your files might be, like your Google Drive, Gmail, Dropbox, and Evernote. Additionally, Meta also has integrations for Slack and Trello, so you can search for files, posts, and code snippets you Slacked to your teammates on a project, or search for specific cards on your Trello boards.


The really cool thing about Meta is that because your files get tagged, you don't even need to know the name of the file to search for it, just some idea of what's in the file. To give you an idea of the tags we got, here are the tags on a tutorial I made for my blog!


Besides the tags we generate, though, you can also add your own custom tags, so you could tag files with which chapter your notes were from, what class a file is for, or do this:


The average worker spends 4 hours a week looking for or rebuilding their files. So hook up Meta to your accounts and get #4extrahours to study, socialize… or watch sloth videos! 


Have an #EpicSemester, and stay slothful!
((.(⊙ω⊙)((.

Sunday, January 3, 2016

Nothing but net(/http)! Fun with HTTP requests in Go

Go's standard net/http library is really popular in the Go community, and for good reasons. Its Handler interface is straightforward, versatile, and easy to learn from Node.js. There are tons of router libraries that build on net/http, like Gorilla Mux and Goji. But besides making web servers, this built-in library also lets you send your own HTTP requests with its HTTP Client type and get and use data from online.

Nothing beats the hacker feeling of sending an HTTP request straight from the black screen, so in this tutorial we're going to try that out with a Client. If you know Go and you're mostly new to HTTP or you want to learn how to send requests from the net/http package, this tutorial is for you!

Also, for the newcomers to HTTP, this tutorial is more focused on programming and doesn't assume much HTTP familiarity, but I highly recommend also reading about HTTP itself to get a better sense of how the Internet works, so I'll link to those at the end of the tutorial. You can find the code from this blog post on this GitHub repository.

Fetching a webpage with a Client
 
If you're reading this, you've been using HTTP clients the whole time. Your browser is basically a really tricked-out HTTP client. When you type in the URL you want to go to, the browser sends an HTTP request to the site's server, the server handles the request and sends back an HTTP response, and then the browser uses the data in the response to render a webpage, as well as doing other browser stuff like making sure you're not downloading a virus, running the site's JavaScript, and putting the site you're going to in your web history.


In Go, a Client from net/http sends the request and gets back the response, but from there on out, what you do with the response is your call. You can fetch a response full of data and plug it into a do data analysis program, you can download pictures, you can take the response and use its bytes as input for LEDs hooked up to a Raspberry Pi to be the life of a dance party, you name it! So for a first example, let's take a look at the response we get back from example.com. Put this code into a file called example.go:
package main

import (
    "fmt"
    "io/ioutil"
    "net/http"
)

func main() {
    // Make a Client and new HTTP request
    client := http.Client{}
    req, err := http.NewRequest("GET", "http://example.com", nil)
    if err != nil {
        fmt.Println("Error creating HTTP request:", err)
        return
    }

    // Send the request and get back the HTTP response
    res, err := client.Do(req)
    if err != nil {
        fmt.Println("Error sending request:", err)
        return
    }
    defer res.Body.Close() // Close the response body to prevent a memory leak

    // Print out the response status code and Content-Type
    fmt.Println("Response status was:", res.Status)
    fmt.Println(res.Header.Get("Content-Type"))
    fmt.Println()

    // Print out the contents of the response body, which is
    // the HTML of example.com
    responseBytes, err := ioutil.ReadAll(res.Body)
    if err != nil {
        fmt.Println("Error getting response body bytes:", err)
    }
    fmt.Println(string(responseBytes))
}

Then in the command line, run it by typing go run example.go and you should get:


What you're seeing is the response, which was the HTML of example.com and a bit of information up top about the HTTP response. Let's break our Go code down and see how we got that response!

First, we create a Client with the line:
client := http.Client{}

Then we can make a request with this code:
req, err := http.NewRequest("GET", "http://example.com", nil)
if err != nil {
    fmt.Println("Error creating HTTP request:", err)
    return
}

This call to NewRequest makes a GET request to http://example.com. Since it's a GET request, we're not sending any data in the request's body, so we give the request a nil request body.

If all goes well, NewRequest gives us a net/http Request. To send it to example.com...
res, err := client.Do(req)
if err != nil {
    fmt.Println("Error sending request:", err)
    return
}
defer res.Body.Close()

We pass our request into our HTTP Client's Do method, which sends the request to example.com. If the request is sent successfully, we will get back the server's Response. We also make sure to close the response body with defer res.Body.Close() to prevent a memory leak.

Now that we have that Response, we can take a closer look at it. These lines tell us what the status of the response was and what type of content is in the response's body:
fmt.Println("Response status was:", res.Status)
fmt.Println("Response content type:", res.Header.Get("Content-Type"))

So those lines are what printed out these messages in the terminal:
Response status was: 200 OK
Response content type: text/html

This tells us that Response has a 200 status code, which basically means the request was successfully sent to the server, and according to its Content-Type header, the data in the response body is HTML. To see the HTML, we use ioutil.ReadAll to turn the response's Body into a byte slice:
responseBytes, err := ioutil.ReadAll(res.Body)
if err != nil {
    fmt.Println("Error getting response body bytes:", err)
}
fmt.Println(string(responseBytes))

Which gives us the HTML of example.com! Here's a diagram of what happens when we send this request from our Client:


As you can see, it's really similar to what happens when we send the request from a browser, except that instead of rendering a webpage from the HTML we just print out the HTML.

Downloading pictures

Webpages aren't the only kind of data you can get from the Internet. There's pictures, videos, JSON data files, code, MP3s, any kind of data you want, you can send and receive over HTTP. And the Go net/http Clients can get responses with these different types of content. For example, let's try downloading a sloth picture. Replace the example.com URL we passed into http.NewRequest with this URL:
"https://upload.wikimedia.org/wikipedia/commons/1/18/Bradypus.jpg"

And when you send the request you should get results that look like this:

That's the encoding of a sloth JPEG but printed out as text. We can't read that, but that's a sloth picture. Indeed, if we print out the Content-Type header, we should get image/jpeg

So we're getting back an image as our response body. Now to turn our program into an image downloader, get rid of the lines printing out the Content-Type header and the Status of the response and run the program again with:
go run sloth-download.go > sloth.jpg

Then open sloth.jpg and you should get:


Now what our program does is it gets the bytes of an image and then prints them to an image file. To trick this out some more, you could also use some image processing code to edit the picture. For example, import the image, image/jpeg, image/color, and os packages and replace this code:
responseBytes, err := ioutil.ReadAll(res.Body)
if err != nil {
    fmt.Println("Error getting response body bytes:", err)
}
fmt.Println(string(responseBytes))

with this:
// Decode the response body into an Image and pass it to purpleJPEG
img, _, err := image.Decode(res.Body)
if err != nil {
    fmt.Println("Error decoding JPEG image: ", err)
    return
}
purpleJPEG(img)

Now instead of simply printing the response body, we make an image.Image from the response's Body and we pass that image into a function called purpleJPEG, which, you guessed it, makes a picture purple:
func purpleJPEG(img image.Image) {
    pixels := image.NewRGBA64(img.Bounds())
    for x := pixels.Bounds().Min.X; x < pixels.Bounds().Max.X; x++ {
        for y := pixels.Bounds().Min.Y; y < pixels.Bounds().Max.Y; y++ {
            r, g, b, a := img.At(x, y).RGBA()
            r = r>>8 + 50
            b = b>>8 + 50
            if r > 256 {
                r = 256
            }
            if b > 256 {
                b = 256
            }
            pixels.Set(x, y, color.RGBA64{
                R: uint16(r >> 8),
                G: uint16(g),
                B: uint16(b >> 8),
                A: uint16(a),})
            }
    }
    jpeg.Encode(os.Stdout, pixels, nil)
}

And now if we run go run purple-sloth-download.go > purple.jpg we will get:



There's actually a bug in purpleJPEG that adds in the green. It's only supposed to give the picture a purple tint, but I decided to keep it in because it looks cooler. If psychedelic rock ever makes a comeback, we better see a band called the Sloth Moths!


So as you can see, running web servers isn't the only powerful functionality of net/http (take note, by the way, that all of the code we used was built with only stuff in the standard library!). The Client type gives you a straightforward interface to send HTTP requests and use the data you get back in your Go code, and we haven't even scratched the surface on what you can do with an HTTP Client.

In addition to receiving data, you can send data in your HTTP requests with POST requests. And we haven't even touched using HTTP requests to communicate with third-party APIs to build apps that integrate with sites like Instagram, Google, Slack, or Twitter, which is what my next tutorial will be about.

HTTP homework

Like I said at the beginning of this tutorial, this blog post was aimed mainly at people who are newer to HTTP. If you're one of them and want to do more with HTTP, like if you want to develop web apps for a startup, it is essential that you build a more solid foundation and de-mystify HTTP so you can work with the protocol both effectively and securely. Below I have a list of links to some resources for getting a better understanding on what HTTP really is:

http://www.tutorialspoint.com/http/ A TutorialsPoint series on HTTP
https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol The HTTP Wikipedia article
https://tools.ietf.org/html/rfc7230 - https://tools.ietf.org/html/rfc7235 The official documents on the HTTP/1.1 protocol
https://golang.org/pkg/net/http/ Go's net/http documentation
http://curl.haxx.se/docs/httpscripting.html The tutorial for cURL, an easy to use command-line HTTP client


Until next time, stay slothful!

Image credits
  • The Go Gopher in the diagrams was originally drawn by Renee French and is licensed under CC BY 3.0
  • The Firefox logo used in the first diagram was made by Mozilla and is licensed under CC BY 3.0
  • The original sloth picture in the tutorial is from Stefan Laube and is public domain.
  • The picture of the web server used in the diragrms was drawn by lyte on openclipart and is public domain.