Testing CLI arguments in go - unit-testing

I want to test some CLI routines in go that are run in the main() function purely, they are just exercises that I'm doing, but I want to make tests on them!
So for example how can I pass arguments to table test this king of algorithm?
package main
import (
"bufio"
"fmt"
"os"
"strconv"
)
func main() {
var f *os.File
f = os.Stdin
defer f.Close()
scanner := bufio.NewScanner(f)
for scanner.Scan() {
if scanner.Text() == "STOP" || scanner.Text() == "stop" {
break
}
n, err := strconv.ParseInt(scanner.Text(), 10, 64)
if err == nil {
fmt.Printf("Number formatted: %d\n", n)
} else {
fmt.Println(err.Error())
}
}
}
I put the code on playground too for better help!
https://play.golang.org/p/JgrQ2yFogNs
Thanks in advance!

You need to create a function which input and output channels as params. It should read and write to these params. Following is an example:
main.go
package main
import (
"bufio"
"fmt"
"io"
"os"
"strconv"
)
func main() {
var f *os.File
f = os.Stdin
defer f.Close()
run (os.Stdin, f)
}
func run(in io.Reader, out io.Writer) {
scanner := bufio.NewScanner(in)
for scanner.Scan() {
if scanner.Text() == "STOP" || scanner.Text() == "stop" {
break
}
n, err := strconv.ParseInt(scanner.Text(), 10, 64)
if err == nil {
fmt.Printf("Number formatted: %d\n", n)
} else {
fmt.Println(err.Error())
}
}
}
main_test.go
package main
import (
"bytes"
"fmt"
"testing"
)
func TestRun(t *testing.T){
var command, result bytes.Buffer
fmt.Fprintf(&command, "10\n")
fmt.Fprintf(&command, "stop\n")
run(&command, &result)
got := result.String()
//test for contents of "got"
fmt.Println(got)
}
Now you can run the following on a command line.
go test

Strictly speaking, you don't need to use Go for this. When I'm looking at writing a CLI tool I break out of main as soon as possible with functions and structs the unit test those in the usual way.
To make sure that all the CLI arguments and file system buts are plumbed in correctly I've been using https://github.com/bats-core/bats-core to run the command and check the fully built tool.
You can see an example of how I've done this at https://github.com/dnnrly/abbreviate
Perhaps not the answer you're looking for but it's been working well for me.

Related

I want to use GoLang's regular expression to perform an ambiguous search for values enclosed in the `*` string

I'm not good at regular expressions.
I want to use fuzzy search to get the value of a key enclosed with * in text.
package main
import (
"fmt"
"log"
"regexp"
)
func main() {
text := "*company* example company!!\n*tel* 09000009999\n*"
regex := fmt.Sprintf(`(?m)\*%s\*\s\s(.+)$`, "company")
rep := regexp.MustCompile(regex)
result := rep.FindAllStringSubmatch(text, -1)
if result != nil {
log.Print(result[0][1])
} else {
log.Print("empty")
}
}
Output results
example company!!
Change the text variable.
package main
import (
"fmt"
"log"
"regexp"
)
func main() {
text := "*company_name* example company!!\n*tel* 09000009999\n*"
regex := fmt.Sprintf(`(?m)\*%s\*\s\s(.+)$`, "company")
rep := regexp.MustCompile(regex)
result := rep.FindAllStringSubmatch(text, -1)
if result != nil {
log.Print(result[0][1])
} else {
log.Print("empty")
}
}
Output results
empty
How do I get company in an ambiguous search?
I want to search for something like "like 'company%'" like in the SQL like clause.
For now, it's done.
package main
import (
"fmt"
"log"
"regexp"
)
func main() {
text := "*1company_name1234* example company!!\n*tel* 09000009999\n*"
regex := fmt.Sprintf(`(?m)\*.*%s.*\*\s\s(.+)$`, "company")
rep := regexp.MustCompile(regex)
result := rep.FindAllStringSubmatch(text, -1)
if result != nil {
log.Print(result[0][1])
} else {
log.Print("empty")
}
}

Making os.Stat throw error such that IsNotExist returns false

I'm trying to test the following branch:
if _, err := os.Stat(path); err != nil {
if os.IsNotExist(err) {
continue
}
return errors.File().AddDetails(err)
}
Obviously, os.Stat is going to throw an error if path doesn't exist. Reading the Golang documentation returns no details about the errors that os.Stat could return. Is there way to have os.Stat throw another kind of error?
You can cause an error such that IsNotExist returns false by passing an invalid filename.
package main
import (
"fmt"
"os"
)
func main() {
_, err := os.Stat("\000x")
fmt.Println(err)
// prints:
//
// stat x: invalid argument
}
\000 (ASCII: NUL) is an invalid character in unix file names.
I think you will find it very difficult to control what errors are thrown and when by os.Stat in a platform independent way from a unit test. If you really need a test for the path where an unknown error type is returned, your best bet might be to refactor your package code so that you can mock os.Stat. Although you can't change the behavior of os.Stat directly, by taking advantage of the fact that Go has first-class functions, you can use a bit of indirection to mock it with very minimal changes to your code. If your package code looks like this:
package mypackage
import "os"
...
if _, err := os.Stat(path); err != nil {
if os.IsNotExist(err) {
continue
}
return errors.File().AddDetails(err)
}
...
Try refactoring it to use a non-exported package-scope function variable which is assigned os.Stat:
package mypackage
import "os"
var osStat = os.Stat
...
if _, err := osStat(path); err != nil {
if os.IsNotExist(err) {
continue
}
return errors.File().AddDetails(err)
}
...
Now, in your test code (which should be in the same package as the code under test), you can reassign osStat to any function with the same signature in order to mock it:
package mypackage
import (
"os"
"testing"
)
func TestNotExistError(t *testing.T) {
osStat = func(string) (os.FileInfo, error) {
return nil, os.ErrNotExist
}
// in case other test functions depend on the unmocked behavior
defer func() {
osStat = os.Stat
}()
// rest of the test which triggers the codepath above
}
func TestOtherError(t *testing.T) {
osStat = func(string) (os.FileInfo, error) {
return nil, os.ErrInvalid
}
// in case other test functions depend on the unmocked behavior
defer func() {
osStat = os.Stat
}()
// rest of the test which triggers the codepath above
}
...

Unit test when receiver method call each other

Imagine there is a User package which contains only two simple methods
Hello which says "Hello"
Say which implements how a user speaks
Original
package user
import "fmt"
type user struct {}
func (u user) Hello() {
u.Say("Hello")
}
func (u user) Say(sentence string) {
fmt.Println(sentence)
}
However, we are not able to unit test Hello since it depends on Say which is not mockable.
After searing on StackOverflow and Goole, I summarize two methods to solve the problem, but none of them are perfect.
Method 1 - Use lambda func
user.go
package user
import "fmt"
type user struct{}
func (u user) Hello() {
say("Hello")
}
func (u user) Say(sentence string) {
say(sentence)
}
var say = func(sentence string) {
fmt.Println(sentence)
}
user_test.go
package user
import (
"testing"
)
func TestHello(t *testing.T) {
sayCalled := 0
sayCallArg := ""
mockSay := func(sentence string) {
sayCalled++
sayCallArg = sentence
}
say = mockSay
u := user{}
u.Hello()
if sayCalled != 1 {
t.Fatalf("not called")
}
if sayCallArg != "Hello" {
t.Fatalf("wrong arg")
}
}
Method 2 - Use interface
user.go
package user
import "fmt"
type user struct {
sayer Sayer
}
func (u user) Hello() {
u.sayer.Say("Hello")
}
func (u user) Say(sentence string) {
u.sayer.Say(sentence)
}
type Sayer interface {
Say(string)
}
type sayer struct{}
func (s sayer) Say(sentence string) {
fmt.Println(sentence)
}
user_test.go
package user
import (
"testing"
)
type mockSayer struct {
called int
calledArg string
}
func (s *mockSayer) Say(sentence string) {
s.called++
s.calledArg = sentence
}
func TestHello(t *testing.T) {
mockSayer := &mockSayer{}
u := user{sayer: mockSayer}
u.Hello()
if mockSayer.called != 1 {
t.Fatalf("not called")
}
if mockSayer.calledArg != "Hello" {
t.Fatalf("wrong arg")
}
}
I understand most of the cases, people will suggest to use Method 2 since that's how dependency injection works in Go.
However, in this example, it's weird to extract the implementation of Say to another layer (unnecessary complexity in my opinion).
Is there any better solution to solve this kind of dependency?
or which method you prefer and why?
None of the above. I don't see where you prove that the Hello method actually works, that "Hello\n" is actually written. Check the Say method output. Mock os.Stdout. For example,
user.go:
package user
import (
"fmt"
"io"
"os"
)
type user struct{}
const hello = "Hello"
func (u user) Hello() {
u.Say(hello)
}
var stdwrite = io.Writer(os.Stdout)
func (u user) Say(sentence string) {
fmt.Fprintln(stdwrite, sentence)
}
user_test.go:
package user
import (
"bytes"
"io"
"testing"
)
func TestHello(t *testing.T) {
u := user{}
u.Hello() // for real
defer func(w io.Writer) { stdwrite = w }(stdwrite)
stdwrite = new(bytes.Buffer)
u.Hello() // for test
got := stdwrite.(*bytes.Buffer).String()
want := hello + "\n"
if got != want {
t.Errorf("want: %q got: %q", want, got)
}
}
Output:
$ go test -v
=== RUN TestHello
Hello
--- PASS: TestHello (0.00s)
PASS
ok say 0.001s

Golang unit testing user input

I am trying to learn go with a TDD mindset. I am stuck getting my head wrapped around testing.
In the example below, I am prompting a user for input, doing a little validation and printing the results. I wrote a test for it (which is passing) however I don't feel like it is hitting the validation portion, so I am doing something wrong. Any advice would be appreciated.
https://play.golang.org/p/FDpbof9Y20
package main
import (
"bufio"
"fmt"
"io"
"os"
"regexp"
"strings"
)
func main() {
response := askQuestion("What is your name?")
fmt.Printf("Hello %s\n",response)
}
func askQuestion(question string) string {
reader := bufio.NewReader(os.Stdin)
answer := ""
for {
fmt.Printf("%s\n", question)
input, err := reader.ReadString('\n')
if err != nil {
if err != io.EOF {
panic(err)
}
break
}
if regexp.MustCompile(`[A-Z]{5}`).MatchString(strings.TrimSpace(input)) == true {
answer = strings.TrimSpace(input)
fmt.Printf("You entered %s\n", answer)
break
} else {
fmt.Printf("\033[31mYou must enter only 5 upper case letters.\n\033[0m")
continue
}
}
return answer
}
https://play.golang.org/p/WcI4CRfle5
package main
import (
"bytes"
"fmt"
"io"
"io/ioutil"
"os"
"reflect"
"strings"
"testing"
)
func TestAskQuestion(t *testing.T) {
expected := "foo"
entered := "foo"
askQuestion("What is your last name?")
oldStdout := os.Stdout
r, w, _ := os.Pipe()
os.Stdout = w
fmt.Println(entered)
outC := make(chan string)
go func() {
var buf bytes.Buffer
io.Copy(&buf, r)
outC <- buf.String()
}()
w.Close()
os.Stdout = oldStdout
out := strings.TrimSpace(<-outC)
b, _ := ioutil.ReadAll(os.Stdin)
t.Log(string(b))
if !reflect.DeepEqual(expected, out) {
t.Fatalf("Test Status Failure Issue. Got: '%v' expected %s", out, expected)
}
}
Go's tests need to live in files which are named xyz_test.go, so the playground is not the right place to familiarize yourself with the unit testing feature.
If you have go installed locally, run the command go help test, to get a very brief introduction.

Golang concurrent download deadlock

I want to download files in parallel in go, but my code never exits:
package main
import (
"fmt"
"io"
"net/http"
"os"
"path/filepath"
"sync"
)
func download_file(file_path string, wg sync.WaitGroup) {
defer wg.Done()
resp, _ := http.Get(file_path)
defer resp.Body.Close()
filename := filepath.Base(file_path)
file, _ := os.Create(filename)
defer file.Close()
size, _ := io.Copy(file, resp.Body)
fmt.Println(filename, size, resp.Status)
}
func main() {
var wg sync.WaitGroup
file_list := []string{
"http://i.imgur.com/dxGb2uZ.jpg",
"http://i.imgur.com/RSU6NxX.jpg",
"http://i.imgur.com/hUWgS2S.jpg",
"http://i.imgur.com/U8kaix0.jpg",
"http://i.imgur.com/w3cEYpY.jpg",
"http://i.imgur.com/ooSCD9T.jpg"}
fmt.Println(len(file_list))
for _, url := range file_list {
wg.Add(1)
fmt.Println(wg)
go download_file(url, wg)
}
wg.Wait()
}
What's the reason? I've looked here: Golang download multiple files in parallel using goroutines but I found no solution.
What is the best way to debug such code?
As Tim Cooper said you need to pass the WaitGroup as a pointer. If you run the go vet tool on your code it will give you this warning:
$ go vet ex.go
ex.go:12: download_file passes Lock by value: sync.WaitGroup contains sync.Mutex
exit status 1
I recommend using an editor that can do this for you when you save a file. For example go-plus for Atom.
As for the code I think you should restructure it like this:
package main
import (
"fmt"
"io"
"net/http"
"os"
"path/filepath"
"sync"
)
func downloadFile(filePath string) error {
resp, err := http.Get(filePath)
if err != nil {
return err
}
defer resp.Body.Close()
name := filepath.Base(filePath)
file, err := os.Create(name)
if err != nil {
return err
}
defer file.Close()
size, err := io.Copy(file, resp.Body)
if err != nil {
return err
}
fmt.Println(name, size, resp.Status)
return nil
}
func main() {
var wg sync.WaitGroup
fileList := []string{
"http://i.imgur.com/dxGb2uZ.jpg",
"http://i.imgur.com/RSU6NxX.jpg",
"http://i.imgur.com/hUWgS2S.jpg",
"http://i.imgur.com/U8kaix0.jpg",
"http://i.imgur.com/w3cEYpY.jpg",
"http://i.imgur.com/ooSCD9T.jpg"}
fmt.Println("downloading", len(fileList), "files")
for _, url := range fileList {
wg.Add(1)
go func(url string) {
err := downloadFile(url)
if err != nil {
fmt.Println("[error]", url, err)
}
wg.Done()
}(url)
}
wg.Wait()
}
I don't like passing WaitGroups around and prefer to keep functions simple, blocking and sequential and then stitch together the concurrency at a higher level. This gives you the option of doing it all sequentially without having to change downloadFile.
I also added error handling and fixed names so they are camelCase.
Adding to Calab's response, there's absolutely nothing wrong with your approach, all you had to do is to pass a pointer to the sync.WaitGroup.
func download_file(file_path string, wg *sync.WaitGroup) {
defer wg.Done()
......
}
.....
go download_file(url, &wg)
.....
playground