Larry Price

And The Endless Cup Of Coffee

Better Testing in Go With Gocheck

| Comments

As a quick reminder, golang is a really fun programming language to use. It even includes testing out of the box! Unfortunately, this out-of-the-box testing framework isn’t all that great. It lacks the syntactic sugar of mature frameworks like rspec or gtest.

Of course, there are alternatives. I found an open-source library (licensed with Simplified BSD) called gocheck.

Gocheck, how do I love thee? Let me count the ways:

  • Test fixtures
  • Improved assertions
  • Improved test output
  • Sugar-coated syntax
  • Test skipping
  • Oh my

As usual, it’s time to guide you through a contrived example. Start by installing the package:

1
$ go get gopkg.in/check.v1

Let’s see, what should we make… how about a tip calculating library? We should start by testing, because we’re obsessed with TDD.

Test #1: Returns 0 for free meal

calculator_test.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package cheapskate

import (
  "testing"
  . "gopkg.in/check.v1"
)

// Hook up gocheck into the "go test" runner.
func Test(t *testing.T) { TestingT(t) }

type MySuite struct{}

var _ = Suite(&MySuite{})

func (s *MySuite) TestReturns0ForFreeMeal(c *C) {
  c.Assert(calculateTip(0.0), Equals, 0.0)
}

Not quite as obvious as the internal testing framework. First we hook up gocheck into the “go test” runner. Then we create a test suite; ours is empty for now and called MySuite. We call Suite to intialize the test runner with our custom suite. We then write our first test to assert that calculating the tip returns a value equal to 0. All tests must be prefixed with the word “Test”. Now I’ll write the implementation:

calculator.go
1
2
3
4
5
package cheapskate

func calculateTip(bill float64) float64 {
  return 0.0
}

Running the tests…

1
2
3
4
$ go test
OK: 1 passed
PASS
ok    _/home/lrp/Projects/2014/gocheck-quick  0.003s

Woohoo! All passed. What happens if we write a failing test?

calculator_test.go
1
2
3
4
...
func (s *MySuite) TestReturns15PercentByDefault(c *C) {
  c.Assert(calculateTip(100.0), Equals, 15.0)
}

Results:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
lrp@cilantro:~/Projects/2014/gocheck-quick$ go test

----------------------------------------------------------------------
FAIL: calculator_test.go:19: MySuite.TestReturns15PercentByDefault

calculator_test.go:20:
    c.Assert(calculateTip(100.0), Equals, 15.0)
... obtained float64 = 0
... expected float64 = 15

OOPS: 1 passed, 1 FAILED
--- FAIL: Test (0.00 seconds)
FAIL
exit status 1
FAIL  _/home/lrp/Projects/2014/gocheck-quick  0.003s

A nasty failure that one. I’ll fix it and continue:

calculator.go
1
2
3
4
5
package cheapskate

func calculateTip(bill float64) float64 {
  return .15 * bill
}

I want to create a Setup method for my entire suite. I’ll store some silly information there for information’s sake. The minBill and maxBill variables will only be set when I first load the suite.

calculator_test.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package cheapskate

import (
  "testing"
  . "gopkg.in/check.v1"
)

// Hook up gocheck into the "go test" runner.
func Test(t *testing.T) { TestingT(t) }

type MySuite struct{
  minBill float64
  maxBill float64
}

func (s *MySuite) SetUpSuite(c *C) {
  s.minBill = 0
  s.maxBill = 100
}

var _ = Suite(&MySuite{})

func (s *MySuite) TestReturns0ForFreeMeal(c *C) {
  c.Assert(calculateTip(s.minBill), Equals, 0.0)
}

func (s *MySuite) TestReturns15PercentByDefault(c *C) {
  c.Assert(calculateTip(s.maxBill), Equals, 15.0)
}

What if I wanted to set some information at the start of each test? I’ll log the current test number on the suite, updating it every time I run a test:

calculator_test.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package cheapskate

import (
  "testing"
  . "gopkg.in/check.v1"

  "fmt"
)

// Hook up gocheck into the "go test" runner.
func Test(t *testing.T) { TestingT(t) }

type MySuite struct{
  testNumber int
}

func (s *MySuite) SetUpTest(c *C) {
  fmt.Println(s.testNumber)
  s.testNumber += 1
}
...

Result:

1
2
3
4
5
6
$ go test
0
1
OK: 2 passed
PASS
ok    _/home/lrp/Projects/2014/gocheck-quick  0.004s

You can create tear down methods for suites and tests in the same manner, replacing the appropriate words above.

There’s loads of other cool stuff gocheck can do, I’ve barely scratched the surface with what little experience I’ve had using it. Like any testing framework, I’m sure it has its advantages and disadvantages, but it sure beats the pants off the off-the-shelf framework Google includes with golang.

The Lean Startup

| Comments

If you cannot fail, you cannot learn.

The Gist

The Lean Startup by Eric Ries explains how Lean methodlogies are not only meaningful for startups, but can also be used to create innovation within large organizations.

What I Think

If I had a nickel for every time the word “entrepreneur” showed up in this book…

Besides that, this book is highly insightful with many great examples of companies, small and large, that have built great products using Lean processes.

Part of what makes this book readable is the credibility of the author, a founder of several startups who has seen failure and success. I wasn’t convinced that I would enjoy this book until I read Eric’s story about IMVU’s initial failures and how those failures were overcome. I won’t spoil too much for anyone currently reading the book, but IMVU does eventually figure out what customers want, thanks in no small part to rethinking the way that the company develops software.

The majority of this book focuses on creating a minimal viable product. Creating an MVP is difficult from a developer’s perspective, and the author touches on this quite a bit. Developers don’t like to release products that may contain bugs or may not contain all the “essential” features, but an MVP is not about feature-completeness, it’s about getting the product to users as quickly as possible to accelerate the “build-measure-learn” process.

Once again, I’d like to stress that the real-world experiences described in this book are what makes it so convincing. Stories about IMVU, Intuit, and Toyota makes these concepts seem obtainable for a company of any size.

Who Should Read This

You should read this if you’re not sure how to get your company thinking about Lean processes. You should also read this if you want to eliminate waste on your project and learn how to get the most out of customer feedback. Open mind required to teach old dogs new tricks.

Using Sqlite on Heroku

| Comments

Or rather, “Not Using sqlite on Heroku.”

Heroku does not support sqlite. That doesn’t mean we have to stop using sqlite in development, but it does mean we need to put in some workarounds to support our deployment environment. The rest of this article will use ruby and Sinatra.

Assuming you have a heroku app deployed and you have sqlite already working locally, this only takes a few steps. First we need to add a SQL database to our heroku app. From the project directory, we’ll add the heroku-postgresql addon to our app.

1
$ heroku addons:add heroku-postgresql:dev

The dev piece of this command tells heroku we want the small, free database. This database supports up to 10,000 rows and has a 99.5% uptime. Best of all: it’s free. Other options have you pay $9/mo for 10,000,000 rows or $50+ for Unlimited usage. I recommend you start small.

Hopefully you got some success statements after adding heroku-postgresql. They should have included some new environment variables, which are links to your new Postgres database. Record these; we’ll use them a little later.

Now we need to set up the back-end to be able to access a Postgres database when necessary. Hopefully you’re using a decent abstraction library in your app that can access any SQL database. For ruby, I find Sequel to be sufficient.

In our Gemfile, we’ve probably already included the sqlite gem for use in our local environment. We can go ahead and move that into a development block, and we need to add the pg gem to either production or the global block.

Gemfile
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
source "https://rubygems.org"

ruby '2.1.0'

gem 'bundler'
gem 'rake'
gem 'sinatra'
gem 'haml'
gem 'sequel'

group :production do
  gem 'pg'
end

group :development do
  gem 'sqlite3'
end

Heroku sets ENV['RACK_ENV'] to “production” for us, which means that the pg gem should get picked up the next time we deploy. Now we need to tell our app which database to use in which situation.

One of the easiest places to make this decision is in Sinatra’s configure block. I keep my local db in an environment variable called LOCAL_DATABASE_URL. This is where you use the environment variable heroku set for you when you set up your Postgres database; mine was called HEROKU_POSTGRESQL_MAROON_URL.

web.rb
1
2
3
4
5
6
7
8
9
class App < Sinatra::Base
  configure :production do
    Sequel.connect ENV['HEROKU_POSTGRESQL_MAROON_URL']
  end

  configure :development do
    Sequel.connect ENV['LOCAL_DATABASE_URL']
  end
end

This works because the default environment is “development.” Test locally, and then we can deploy.

1
$ git push heroku master

And enjoy.

Rework

| Comments

You want to get into the rhythm of making choices. When you get in that flow of making decision after decision, you build momentum and boost morale. Decisions are progress. Each one you make is a brick in your foundation. You can’t build on top of “We’ll decide later,” but you can build on top of “Done.”

Excerpt from “Decisions are Progress” in Rework

The Gist

Rework by Jason Fried and David Heinemeier Hansson is a business advice book for keeping small, focused teams without all the cruft of business.

My Opinion

Rework is my kind of book. It’s best described as business advice for hippy programmers. It’s amazing that this book describes the business practices of a real company that’s modestly successful.

Speaking of the authors of this book, they’re the guys who made Ruby on Rails, which gives them street cred as far as I’m concerned.

This is a book of proverbs. Ignore the real world. Start a business, not a startup. Build half a product, not a half-assed product. Meetings are toxic. Hire managers of one. ASAP is po:son.

When you look at it, this advice is not insightful: it’s obvious. It just also happens to be against how modern-day companies work. And maybe this advice isn’t useful for everyone; once a company stops following these rules, it seems difficult to change. The smaller and newer the company, the easier it is to avoid the issues introduced with bigger businesses.

This book talks about ideas being cheap. Implementing the idea is where all the work lies. Have an idea? Start working on it now. Find time. Go to bed an hour later. Watch an hour less of TV. Your idea is meaningless unless you actually follow through.

There’s also good advice within this book for people who are pressured to overwork. Avoid workaholics: overworking hurts the decision-making processes, so people who are working more hours are just spending more hours making worse decisions. When employees work long hours, other employees are also influenced to work longer hours. More poeple making worse decisions? Count me out. Hire people with outside lives who go home at 5: busy people have to manage their time, and will thus find more efficient ways to accomplish their work.

Who Would Like This

Interested in starting a company? In making managerial changes within your current company? In making changes to the way you work? Reading this book can help you realize that you don’t need all the modern-day business cruft to make your business a success.

Sticky Footer With Twitter Bootstrap

| Comments

Sometimes CSS is a total pain.

We encountered a major CSS problem while working on our incredible startup weekend project Ollert. We had created a footer that we wanted below all of our content. We threw together some quick HTML and got a footer below all of the main content, and it looked really good when our main content filled up the entire screen.

What about when there was very little data on the screen? Well, then the footer just floated in the middle of the page, staring at us like some kind of psychotic hummingbird, waiting to slice you up when you’re not looking. We searched online and found lots of different solutions; None of them worked. The footer just floated there, taunting us; Telling us to cry home to mommy. So we gave up on the prospect for the rest of the afternoon.

A few days after startup weekend, I found the real solution from the good folks at Twitter Bootstrap themselves. It’s pretty simple, really. Hooray for the internet!

Below is the HTML to create this effect with all the CSS styles embedded. Marked up with plenty of comments.

index.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<!DOCTYPE html>
<html lang="en" style="height: 100%;">
  <head>
    <link rel="stylesheet" href="http://netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css">
  </head>
  <body style="height: 100%;">
    <div id="wrap" style="min-height: 100%; height: auto !important; height: 100%; margin: 0 auto -50px;">
      <!-- All your content goes inside wrap. -->
      <!-- The bottom margin must be negative the footer min-height -->
      <!-- Footer min-height is set to 50px in this case -->
      <div class="container">
        <div class="row">
          <div class="h1">
            All Your Content
          </div>
        </div>
        <div class="row">
          All your content will go inside the 'wrap' div.
        </div>
      </div>
      <div id="push" style="min-height: 50px;">
        <!-- This push node should be inside wrap and empty -->
        <!-- Min-height is equal to the min-height of the footer -->
      </div>
    </div>
  </body>
  <div id="footer" style="min-height: 50px;">
    <!-- Some sweet footer content -->
    <div class="container">
      <div class="small">
        Zowee, I've got a footer stuck to the bottom!
      </div>
    </div>
  </div>
</html>

Div tag ids such as “wrap”, “push”, and “footer” can be whatever you want. The height of the footer can be adjusted to fit whatever content you want; I found that using min-height instead of height allowed my content to resize appropriately when wrapped. Styles should definitely be moved to a css file.