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.