Test Driven Development (TDD) in GoLang

Test Driven Development (TDD) is a software development approach where tests are written before the actual code. In this blog post, I will demonstrate the development of a simple grep command-line tool using GoLang with TDD.

Step 1: Setting Up the Environment

First, we need to install GoLang. You can download and install GoLang from the official website (https://golang.org/dl/).

Once GoLang is installed, we can set up the project directory and install the necessary packages. The following commands can be executed in the terminal to set up the project:

mkdir grep-tdd
cd grep-tdd
go mod init grep-tdd
go get github.com/stretchr/testify/assert

Here, we have created a new directory named grep-tdd, initialized a new Go module named grep-tdd, and installed the testify/assert package which we will be using for testing.

Step 2: Creating the grep command-line tool

Now, we can start creating the grep command-line tool. In this example, we will create a simple grep command-line tool that will take a regex pattern and a file path as input and prints the matching lines in the file.

Create a new file named grep.go in the project directory and add the following code:

package main

import (
    "bufio"
    "fmt"
    "os"
    "regexp"
)

func main() {
    if len(os.Args) != 3 {
        fmt.Printf("Usage: go run grep.go <pattern> <file>\\\\n")
        os.Exit(1)
    }

    pattern := os.Args[1]
    filePath := os.Args[2]

    file, err := os.Open(filePath)
    if err != nil {
        fmt.Printf("Error opening file: %v\\\\n", err)
        os.Exit(1)
    }
    defer file.Close()

    scanner := bufio.NewScanner(file)
    for scanner.Scan() {
        line := scanner.Text()
        match, _ := regexp.MatchString(pattern, line)
        if match {
            fmt.Println(line)
        }
    }

    if err := scanner.Err(); err != nil {
        fmt.Printf("Error scanning file: %v\\\\n", err)
        os.Exit(1)
    }
}

Here, we have defined the main function which takes two arguments from the command line, a regex pattern and a file path, and prints the matching lines in the file. If the input is invalid or the number of arguments is less than 2, an error message is displayed.

Step 3: Writing Tests

Now, we can write tests for our grep command-line tool using TDD. Create a new file named grep_test.go in the project directory and add the following code:

package main

import (
    "os"
    "testing"

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

func TestMain(t *testing.T) {
    os.Args = []string{"cmd", "hello", "testdata/test.txt"}
    main()

    expected := "hello world\\\\nhello golang\\\\n"
    actual := captureOutput(main)

    assert.Equal(t, expected, actual)
}

func TestMainInvalidInput(t *testing.T) {
    os.Args = []string{"cmd", "hello", "testdata/non-existent-file.txt"}
    exit := captureExit(main)

    assert.Equal(t, 1, exit)
}

func captureOutput(f func()) string {
    rescueStdout := os.Stdout
    r, w, _ := os.Pipe()
    os.Stdout = w

    f()

    w.Close()
    out, _ := readAll(r)
    os.Stdout = rescueStdout

    return string(out)
}

func captureExit(f func()) int {
    rescueExit := os.Exit
    defer func() { os.Exit = rescueExit }()
    var code int
    os.Exit = func(c int) {
        code = c
    }
    f()
    return code
}

func readAll(r *os.File) ([]byte, error) {
    var buf []byte
    tmp := make([]byte, 1024)
    for {
        n, err := r.Read(tmp)
        if err != nil {
            return nil, err
        }
        buf = append(buf, tmp[:n]...)
        if n < len(tmp) {
            return buf, nil
        }
    }
}

Here, we have defined two test cases:

  • TestMain: This test case checks if the correct output is returned for valid input.

  • TestMainInvalidInput: This test case checks if the program exits with an error for a non-existent file.

We are using the testify/assert package to assert the expected and actual values.

Step 4: Running the Tests

Now, we can run the tests using the following command in the terminal:

go test -v

Here, the -v flag is used to display the verbose output.

Conclusion

In this blog post, we have demonstrated the development of a simple grep command-line tool using GoLang with Test Driven Development (TDD). TDD helps in building robust and maintainable code by ensuring that all the code is tested.