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.
Related
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")
}
}
I'm learning tests in Go and I have been trying to measure test coverage in an API that I created:
main.go
package main
import (
"encoding/json"
"log"
"net/http"
)
func main() {
http.HandleFunc("/", SimpleGet)
log.Print("Listen port 8080")
log.Fatal(http.ListenAndServe(":8080", nil))
}
// SimpleGet return Hello World
func SimpleGet(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/" {
http.NotFound(w, r)
}
w.Header().Set("Content-Type", "application/json")
data := "Hello World"
switch r.Method {
case http.MethodGet:
json.NewEncoder(w).Encode(data)
default:
http.Error(w, "Invalid request method", 405)
}
}
And the test:
main_test.go
package main
import (
"net/http"
"net/http/httptest"
"strings"
"testing"
)
func TestSimpleGet(t *testing.T) {
req, err := http.NewRequest("GET", "/", nil)
if err != nil {
t.Fatal(err)
}
w := httptest.NewRecorder()
SimpleGet(w, req)
resp := w.Result()
if resp.Header.Get("Content-Type") != "application/json" {
t.Errorf("handler returned wrong header content-type: got %v want %v",
resp.Header.Get("Content-Type"),
"application/json")
}
if status := w.Code; status != http.StatusOK {
t.Errorf("handler returned wrong status code: got %v want %v", status, http.StatusOK)
}
expected := `"Hello World"`
if strings.TrimSuffix(w.Body.String(), "\n") != expected {
t.Errorf("handler returned unexpected body: got %v want %v", w.Body.String(), expected)
}
}
When I run go test it is fine, the test has passed. But when I try to get the test coverage, I got this HTML:
I would like to understand what is happened here because it has not covered anything. Does anyone know to explain?
I have found my error:
I was trying to run the test coverage with these commands:
$ go test -run=Coverage -coverprofile=c.out
$ go tool cover -html=c.out
But the correct commands are:
$ go test -coverprofile=c.out
$ go tool cover -html=c.out
Result:
OBS: I wrote one more test to cover all switch statements. Thanks for all, and I'm sorry if I disturbed someone.
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.
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
I have a simple program in Go to aid in learning regular expressions. It runs in an infinite loop and has 2 channels, one which is used to provide input (input contains regex pattern and subject), and the second one, which provides the output.
usage: main.exe (cat)+ catcatdog
However there is propably something wrong in the code, as i can't seem to get any results with the $ modifier.
For example, i expect "cat" output from
main.exe cat$ cat\ndog
yet receive zero results.
Code:
package main
import (
"fmt"
"regexp"
"bufio"
"os"
"strings"
)
type RegexRequest struct {
regex string
subject string
}
func main() {
regexRequests := make(chan *RegexRequest)
defer close(regexRequests)
regexAnswers, err := createResolver(regexRequests)
defer close(regexAnswers)
if(err != nil) { // TODO: Panics when exited via ctrl+c
panic(err)
}
interact(regexRequests, regexAnswers)
}
func interact(regexRequests chan *RegexRequest, regexAnswers chan []string) {
for {
fmt.Println("Enter regex and subject: ")
reader := bufio.NewReader(os.Stdin)
line, err := reader.ReadString('\n')
if(err != nil) {
panic(err)
}
regAndString := strings.SplitN(line, " ", 2);
if len(regAndString) != 2 {
fmt.Println("Invalid input, expected [regex][space][subject]")
continue
}
regexRequests <- &RegexRequest{ regAndString[0], regAndString[1] }
result := <- regexAnswers
var filteredResult []string
for _, element := range result {
if(element != "") {
filteredResult = append(filteredResult, element)
} else {
filteredResult = append(filteredResult, "EMPTY");
}
}
fmt.Println(strings.Join(filteredResult, " "))
}
}
func createResolver(inputChan chan *RegexRequest)(outputChan chan []string, err error) {
if(cap(inputChan) > 0) {
return nil, fmt.Errorf("Expected an unbuffered channel")
}
outputChan = make(chan []string)
err = nil
go func() {
for {
var regReq *RegexRequest= (<- inputChan);
var regex *regexp.Regexp = regexp.MustCompile(regReq.regex)
outputChan <- regex.FindAllString(regReq.subject, -1)
}
}()
return
}
Check your regex pattern. For example,
Enter regex and subject:
cat$ cat\ndog
Enter regex and subject:
^cat cat\ndog
cat