2023 09 05

A single step

This is the first post. I know that “a journey of a thousand miles starts with a single step,” but I’ve been really putting off getting started on this for some reason, probably from a fear of doing it “wrong”. But the truth is, I definitely will. “Don’t let perfect be the enemy of good”, they say. I could probably sit here and list off sayings all day, but that’s probably just more procrastination. I’ve been an engineer for over 12 years, but I have to admit, most days I don’t know what I’m doing. Which is the point of this blog. This is my attempt to learn in public, so let’s get started.

Where to begin

I’ve decided to start by building a testing framework. This is a logical place to start, because I think all great code is written test-first. There are a ton of reasons for this (including that writing code test-first tends to lead to better-designed and better-written software in the first place, but more about that in a different post), but to me personally, the strongest argument in favor of Test-Driven Development (TDD) is that, well, this ensures that all of your interfaces have automated tests. This is critical for good software, because the most important thing about software is that it’s soft, able to continually evolve and change and improve. If your software isn’t throroughly covered by automated tests, you’ll hesitate to change it–which means you’ll hesitate to improve it–for fear of unknowingly breaking something. Thorough test coverage frees you to refactor and improve your code forever with much less hassle and fewer and fewer defects every time. This is a virtuous cycle that ensures code gets better with time, which is the precise opposite of the natural trend.

My plan is to build a simple framework based on the xUnit model, following the discussion given in Kent Beck’s great book Test Driven Development. I’m going to begin writing it in Go, mostly because Go is simple and was built with the web in mind. That said, I’m using Go for now to see how it feels as a tool for developing this particular project, and I reserve the right to change the implementation to use a different language, or to use multiple languages, in the future. That applies to all projects going forward. Again, this is meant to be a learning experience, and an exploration of different languages along with which tools are the best for which jobs is wrapped up in that. This is not a Go blog, it’s an engineering blog, and my hope is that this can be a language-agnostic resource for learning about what’s under the hood of the internet.

A note on style

I believe that having a column limit in your code is appropriate, but I think that limit should be large, given the high resolution and large width of most modern displays. Because I think using descriptive names is much more important than brevity, I believe that enforcing an artificially low column limit will lead to code that is difficult to write and has the potential to be difficult to read. For that reason, I’ve set a vertical ruler in my IDE at 120 characters, which may be larger than some people might typically prefer. I may lower this in the future if I find that the additional length beyond 80 or 100 feels unnecessary.

Let’s get started

I like trees, so I’ve decided to name all of the projects built as a part of this blog after some of their different types. This testing framework will be called Evergreen (notice the triple meaning), and the public repository will be viewable at https://github.com/RockLikeAmadeus/evergreen.

We’re going to build our test framework test-first, and we’re going to test our testing framework using our testing framework. Yeah.

We could, of course, use Go’s built-in testing framework, but we’re trying things this way because this is how Beck does it in his book. So prepare to get meta.

TDD is done one tiny step at a time, and we need to do this first tiny step by hand, since we don’t have a harness to test in (I know we do, but for the sake of the “from scratch” brand, just pretend with me). That first tiny step is to implement the ability to actually run a test method. So our first (semi-manual) test will look like this:

func main() {
	test := newTest("testMethod")
	fmt.Println(test.wasRun) // Should print "false"
	test.testMethod()
	fmt.Println(test.wasRun) // Should print "true"
}

The operator := is called the short declaration operator. It has various rules regarding its usage, but the important point here is that it allows you to declare and initialize a variable without using the var keyword. Here, we instantiate a new instance of the (as of yet non-existent) test type, and check the value of its wasRun property before and after running its testMethod().

Of course, this won’t even compile as is, but this is typical of test-driven development: your test code should call the code it wishes to test, even if the classes or methods don’t yet exist. Doing so allows you to specify the interfaces as you would like them to be used, which leads to better designed code.

At this point, we have a failing test (and not compiling counts as failing). This is always the first step of the TDD cycle. The next step is to get to green (passing). We’ll define the test type, it’s “constructor” (although Go doesn’t use traditional constructors), and define its single method testMethod():

type test struct {
	name   string
	wasRun bool
}

func newTest(name string) *test {
	test := test{name: name}
	test.wasRun = false
	return &test
}

func (t *test) testMethod() {
	t.wasRun = true
}

Note that all these names all begin with a lowercase letter. That is because, for now, we have everything in the same file, so we can (and should) leave them local to the file so long as they don’t need to be exported. Also notice the * before the declaration of the receiver type. This is called a pointer receiver, and is necessary for us to modify the values of the instance from within the method. For more info on basic Go programming, see Resources.

Running this code will give us the result we expect (a passing test!), but it doesn’t complete the TDD loop. We still need to refactor. We’ll save this and more for next time.


Thanks for reading!

Alec