#0. 테이블 드리븐 테스트에서 사용자 입력에 대한 고루틴 처리 테스트

채널을 만들어주고 입력(stdin)에 데이터를 써주고(write) 고루틴과 채널을 사용하는 함수에 대한 테스트

 

#1. 실습

maim.go와 main_test.go로 테스트 실습.

package main

import (
    "bufio"
    "fmt"
    "os"
    "strconv"
    "strings"
)

func main() {
    // print a welcome message
    intro()
    // create a channel to indicate when the user wants to quit
    doneChan := make(chan bool)
    // start a goroutine to read user input and run program
    go readUserInput(doneChan)
    // block until the doneChan gets a value
    <-doneChan
    // close the channel
    close(doneChan)
    // say goodbye
    fmt.Println("Goodbye.")
}

func readUserInput(doneChan chan bool) {
    scanner := bufio.NewScanner(os.Stdin)
    for {
       res, done := checkNumbers(scanner)
       if done {
          doneChan <- true
          return
       }
       fmt.Println(res)
       prompt()
    }
}

func checkNumbers(scanner *bufio.Scanner) (string, bool) {
    // read user input
    scanner.Scan()
    //check to see if the user wants to quit
    if strings.EqualFold(scanner.Text(), "q") {
       return "", true
    }
    // try to convert what the user typed into an int
    numToCheck, err := strconv.Atoi(scanner.Text())
    if err != nil {
       return "Please enter a whole number", false
    }
    _, msg := isPrime(numToCheck)
    return msg, false
}
func intro() {
    fmt.Println("Is it Prime?")
    fmt.Println("------------")
    fmt.Println("Enter a whole number, and we'll tell you if it is a prime number or not. Enter q to quit.")
    prompt()
}
func prompt() {
    fmt.Print("-> ")
}
func isPrime(n int) (bool, string) {
    // 0과 1 처리(0과 1은 소수가 아님)
    if n == 0 || n == 1 {
       return false, fmt.Sprintf("%d is not prime, by definition!", n)
    }
    // 0보다 작은 수의 경우
    if n < 0 {
       return false, "Negative numbers are not prime, by definition!"
    }
    // 소수인지 아닌지 체크
    for i := 2; i <= n/2; i++ {
       if n%i == 0 {
          // 소수가 아닌 경우
          return false, fmt.Sprintf("%d is not a prime number because it is divisible by %d!", n, i)
       }
    }
    //소수인 경우
    return true, fmt.Sprintf("%d is a prime number!", n)
}

1. main.go 파일

소수인지 체크하는 프로그램으로써. 소수인 경우, 소수가 아닌 경우, 0과1인 경우, 0보다작은 경우(-1)를 체크한다.

그리고 사용자에게 입력을 받고 문자열을 출력하는 함수(intro(), prompt(), checkNumbers(), readUserInput())들을 추가했다.

 

package main

import (
	"bufio"
	"bytes"
	"io"
	"os"
	"strings"
	"testing"
)

func Test_isPrime(t *testing.T) {
	primeTests := []struct {
		name     string
		testNum  int    //테스트 할 값
		expected bool   //예상되는 bool값
		msg      string //예상되는 메시지
	}{
		{"prime", 7, true, "7 is a prime number!"},
		{"not prime", 8, false, "8 is not a prime number because it is divisible by 2!"},
		{"zero", 0, false, "0 is not prime, by definition!"},
		{"one", 1, false, "1 is not prime, by definition!"},
		{"negative number", -11, false, "Negative numbers are not prime, by definition!"},
	}

	for _, e := range primeTests {
		result, msg := isPrime(e.testNum)

		//true가 예상되지만 false를 반환한 경우
		if e.expected && !result {
			t.Errorf("%s: expected true but got false", e.name)
		}
		//false가 예상되지만 true를 반환한 경우
		if !e.expected && result {
			t.Errorf("%s: expected false but got true", e.name)
		}

		//예상되는 e.msg값과 반환한 msg값이 다른 경우
		if e.msg != msg {
			t.Errorf("%s: expected %s but got %s", e.name, e.msg, msg)
		}
	}
}

func Test_prompt(t *testing.T) {
	// save a copy of os.Stdout
	oldOut := os.Stdout

	// create a read and write pipe
	r, w, _ := os.Pipe()

	// set os.Stdout to our write pipe
	os.Stdout = w

	prompt()

	// close our write
	_ = w.Close()

	// reset os.Stdout to what it was before
	os.Stdout = oldOut

	// read the output of our prompt() func from our read pipe
	out, _ := io.ReadAll(r)

	// perform our test
	if string(out) != "-> " {
		t.Errorf("incorrect prompt: expected -> but got %s", string(out))
	}
}

func Test_intro(t *testing.T) {
	// save a copy of os.Stdout
	oldOut := os.Stdout

	// create a read and write pipe
	r, w, _ := os.Pipe()

	// set os.Stdout to our write pipe
	os.Stdout = w

	intro()

	// close our write
	_ = w.Close()

	// reset os.Stdout to what it was before
	os.Stdout = oldOut

	// read the output of our prompt() func from our read pipe
	out, _ := io.ReadAll(r)

	if !strings.Contains(string(out), "Enter a whole number") {
		t.Errorf("intro text not correct; got %s", string(out))
	}
}

func Test_checkNumbers(t *testing.T) {
	tests := []struct {
		name     string
		input    string
		expected string
	}{
		{name: "empty", input: "", expected: "Please enter a whole number!"},
		{name: "zero", input: "0", expected: "0 is not prime, by definition!"},
		{name: "one", input: "1", expected: "1 is not prime, by definition!"},
		{name: "two", input: "2", expected: "2 is a prime number!"},
		{name: "three", input: "3", expected: "3 is a prime number!"},
		{name: "negative", input: "-1", expected: "Negative numbers are not prime, by definition!"},
		{name: "typed", input: "three", expected: "Please enter a whole number!"},
		{name: "decimal", input: "1.1", expected: "Please enter a whole number!"},
		{name: "quit", input: "q", expected: ""},
		{name: "QUIT", input: "Q", expected: ""},
	}
	for _, e := range tests {
		input := strings.NewReader(e.input)
		reader := bufio.NewScanner(input)
		res, _ := checkNumbers(reader)

		if !strings.EqualFold(res, e.expected) {
			t.Errorf("%s: expected %s, but got %s", e.name, e.expected, res)
		}
	}
}

func Test_readUserInput(t *testing.T) {
	// to test this function, we need a channel, and an instance of an io.Reader
	doneChan := make(chan bool)

	// create a reference to a bytes.Buffer
	var stdin bytes.Buffer

	stdin.Write([]byte("1\nq\n"))

	go readUserInput(&stdin, doneChan)
	<-doneChan
	close(doneChan)
}

 

2. main_test.go 파일 (func Test_readUserInput)

 1. doneChan := make(chan bool) : readUserInput(고루틴)과 연결하기 위한 채널 생성.

 2. var stdin bytes.Buffer : 임의로 입력할 값을 넣기 위한 가변버퍼 생성.

 3. stdin.Write([]byte("1\nq\n")) : 가변버퍼에 1\n(엔터), q\n(엔터)를 넣어 줍니다.

 4. q\n : if done { doneChan <- true return } 이 부분이 coverd.

 5. 1\n : fmt.Println(res), prompt() 이 부분이 covered.

 6. go readUserInput(&stdin, doneChan) : 고루틴으로 readUserInput 함수를 실행

 7. <-doneChan : 동시에 main에서 채널에 값이 올때 까지 대기

 8. close(doneChan) : 채널을 닫아줌

 

#2. 결과

테스트통과

1. go test -v, go test -cover 명령어로 테스트 범위와 통과 확인.

 

html 파일로 테스트 cover 확인

 

2. go test . -coverprofile=coverage.out 명령어로 coverage.out 파일을 만들어줌
3. go tool cover -html=coverage.out(윈도우에서는 .out없이)  명령어로 html 파일로 확인. (초록색 부분이 cover(테스트 코드 작성 완료))

 

출처
https://www.udemy.com/course/introduction-to-testing-in-go-golang/

+ Recent posts