Monday, September 30, 2013

Behavior-driven testing in Go with GoConvey (BDD in "golang")

First: the built-in Go testing tools

Few things bring sweeter peace to the soul than making changes to Go code, then:

$ go test
...
PASS
ok

Go's built-in testing tools are good, but their structure doesn't easily allow for us to convey behavior. When we make an assertion, we assert that the opposite is not true instead of asserting that what we want is true:

if (a != b) {
    t.Errorf("Expected '%d' but got '%d'", a, b)
}

This has been very nice, but as projects get bigger, tests get more numerous, and structuring them so they are readable, avoid repetition, and clearly test specific behavior rather than just results becomes difficult to do idiomatically. (Plus, Go's default test output is hard to read.)

Enter GoConvey.

GoConvey is a library for behavior-driven development. It implements the same go test interface you're used to, and plays nicely in both default and verbose (go test -v) modes. GoConvey structures your tests in a way that makes them more readable and reliable. GoConvey has easy-to-read output that even uses colors (and if you're on Mac, Unicode easter eggs) for fast comprehension.

Finally: Your first GoConvey tests

Simply install GoConvey with:

$ go get github.com/smartystreets/goconvey

Then open your new test file. Be sure to name it something ending with "_test.go". It should look familiar to start (I'll be recreating this example file):

package examples

import (
. "github.com/smartystreets/goconvey/convey"
"testing"
)

func TestSpec(t *testing.T) {
}

Notice that we import the "convey" package from GoConvey with the dot notation. This is an acceptable practice for our purposes, as you will see, since this is all for testing your production code and none of it actually gets built into your executable.

To start testing, let's begin to fill out that TestSpec func:

func TestSpec(t *testing.T) {
Convey("Subject: Increment and decrement", t, nil)
}

This sets up our first test harness. We pass in a string which acts merely as a comment, then the testing.T object (but only our top-level Convey() call should have it! -- you'll see in a minute), then nil. For now, this means "nothing to see here, move along" -- and that test will be skipped. In order to be useful, we must replace nil with a func():

func TestSpec(t *testing.T) {
Convey("Subject: Increment and decrement", t, func() {
var x int

Convey("When incremented", func() {
x++
})
})
}

As you can see, any nested calls to Convey() shouldn't have "t" passed in.

Within your functions, you can set up your test at the appropriate scope. Above, we've defined a function to test the subject ("Increment and decrement") and given it an int to work with (x). You may want to avoid using := notation at higher scopes until you're done nesting Conveys. (Figuring out why is left as an exercise for the reader.)

Our second level of Convey, then, tests various paths or situations that the subject may encounter. The harness we've specified tests the paths when x is incremented. Now it's time to make assertions. We'll make two:

func TestSpec(t *testing.T) {
Convey("Subject: Integer incrementation and decrementation", t, func() {
var x int

Convey("Given a starting integer value", func() {
x = 42

Convey("When incremented", func() {
x++

Convey("The value should be greater by one", func() {
So(x, ShouldEqual, 43)
})
Convey("The value should NOT be what it used to be", func() {
So(x, ShouldNotEqual, 42)
})
})
})
})
}

The nested structure is incredibly helpful as projects grow.

Feel free to make several assertions in a row, within one convey, or in a loop. Check marks will be placed at the end of the verbose output to indicate that each one has passed (or an X if it didn't pass).

Oh -- did I mention that you can now run your tests by doing:

$ go test

I usually prefer verbose mode:

$ go test -v

And if you want your tests to run automatically when you save your test files:

$ python $GOPATH/src/github.com/smartystreets/goconvey/scripts/idle.py

Similarly, if you want verbose mode, tack a -v on to the end of that.

Available functions / assertions

This primer ends here, but that should get you started with BDD in Go. Be sure to check out the README for another once-over, and the Godoc documentation on the assertions and methods you can use, since I didn't cover most of them here. Also see the examples folder for even more that aren't yet documented, such as a Reset() function and similar things.

GoConvey is a new library but has lots of promise for writing more robust and test-documented code. As you encounter certain needs that aren't yet met by the library, open an issue or fork it and contribute.

Friday, September 6, 2013

How to set up dynamic DNS in 5 minutes

With a dynamic DNS service (don't worry, there are free ones), you can use your domain name to point it to your home server, even if your home IP address changes sometimes. That way you can type yourdomain.com in your browser and connect to your home network, or any other machine you configure.

All the guides I found for this simple task were actually really hard and required installing and configuring obscure/old software. I'll show you how to do it without installing anything and you can be running in about 5 minutes.

Of course, I assume you're on a Mac or Linux computer. If you're running Windows, you'll probably have to install something. Also, I used Namecheap's free, built-in dynamic DNS service, and it's the only one I've tried so far.

I bet you can get this done in 2 easy steps!

  1. Enable dynamic DNS (I'll show you how with Namecheap)
  2. Set up automatic updates so your domain points to your home (or whatever)

1. Enable dynamic DNS

A dynamic DNS service is one which has access to modify your domain name and update the host (IP address) it points to.

I mainly use Namecheap for my domain registrations. Conveniently, they also have a free dynamic DNS service. (There are plenty of paid ones out there, but you may not need their features. I don't.) What follows is how I do this on Namecheap, but any registrar with a similar service probably works similarly.

Click on the domain name in your Namecheap account and make sure the domain name's nameservers are pointed to Namecheap. Then click "All Host Records" in the menu. I want my entire domain to point to my home server, so I set @ and www like so:

Your host records might look something like this
You may choose to use only a subdomain, in which case you'd just fill out a new row below in a similar fashion with the sub-domain (host name) of your choice.

Either way, the IP address you enter isn't too important right now. I just chose 127.0.0.1.

Save those settings, then click "Dynamic DNS" near the bottom of the menu:

Namecheap's built-in dynamic DNS works just fine
Make sure the "enable" option is checked and click the button to actually enable dynamic DNS.

Almost done! Keep that tab open, because we'll need the password given on that page.

2. Set up automatic updates

Now we just need to have "E.T. phone home" occasionally, so to speak. This means your home server will call out to your dynamic DNS provider and say "I'm here!" and give it your current IP address.

Most guides will tell you how to install and configure a dynamic DNS client -- but we're just going to use cURL and a cron job for this. (Okay, so if you don't have cURL already installed for some reason, then install it.)

If you're on Mac or Linux, this is easy enough with crontab. All you have to do is make a web request every so often. For example, you might add this to your crontab, if you're using Namecheap:

@hourly curl "https://dynamicdns.park-your-domain.com/update?host=HOST_NAME_HERE&domain=YOUR_DOMAIN_HERE&password=YOUR_PASSWORD_HERE&ip=`curl -s echoip.com`"

(NameCheap's documentation.) You can choose how frequently to have it run. Now let me explain the URL which you need to customize:
  • HOST_NAME_HERE - This would be the host name from the "All Host Records" page. If you're using the whole domain name (yourdomain.com), use @. If you're using the www prefix (www.yourdomain.com), then add a cron job that's exactly the same except with the host of www. Otherwise, this value would be the sub-domain you chose to use and made the A record for.
  • YOUR_DOMAIN_HERE - This is your actual domain name, e.g.: yourdomain.com
  • YOUR_PASSWORD_HERE - This is the password that Namecheap gives you on their "Dynamic DNS" page. 
The last parameter actually specifies the IP address to use. This little trick gets your public IP address using echoip.com.

It's important to note that anyone who has your password can set any host name on your domain with an A (Address) record to any IP address they want, effectively hijacking your domain name.

If you use another dynamic DNS provider or registrar, I'm not sure what the request URL would be for you. That's something you'd have to find out from the organization.

So there you go! Give it a few minutes (maybe a few hours) to propagate, and then your domain will be pointing to your own server.