#0. 테이블 드리븐 테스트에서 터미널에 출력하는 함수 테스트

os.stdout에 표준출력을 우리의 write pipe로 바꿔주고, 바꿔준 pipe로 문자열을 읽어들여서 비교해서 테스트.

 

#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 (
    "io"
    "os"
    "testing"
)

func Test_isPrime(t *testing.T) {
    primeTests := []struct {
       name     string
       testNum  int
       expected bool
       msg      string
    }{
       // 1. testNum의 이름, 2. 테스트할 값, 3. 예상되는 값(bool), 4. 예상되는 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)
       // 예상되는 값(bool)이 true 이지만 false를 얻은 경우 테스트 실패
       if e.expected && !result {
          t.Errorf("%s: expected true but got false", e.name)
       }
       // 예상되는 값(bool)이 false 이지만 true를 얻은 경우 테스트 실패
       if !e.expected && result {
          t.Errorf("%s: expected false but got true", e.name)
       }

       // 반환된 값(string)과 예상되는 값(string) 메세지가 다른 경우 테스트 실패
       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 writer

    _ = 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))
    }
}

2. main_test.go 파일 (func Test_prompt)

 1. oldOut 변수에 표준출력을 저장시켜 놓음.

 2. os.pipe() 함수를 통해서 읽거나 쓸수 있는 파일 쌍을 반환

 3. os.Stdout = w, 표준출력을 설정한 os.Stdout에 os.Pipe로 반환한 w(파일에 쓸수 있는)를 적용시킴

 4. prompt()를 실행 시켜서 터미널에 출력하던 문자열을 w로 출력함.

 5. w.Close()로 w를 닫아줌.

 6. os.Stdout = oldOut을 통해서 다시 표준출력으로 복구시킴.

 7. out, _ := io.ReadAll(r), 4번을 통해서 실행 시킨 문자열을  out으로 모두 읽어옴

 8. if string(out) != "-> ", prompt 함수에서 출력하는 문자열이 맞는지 확인.

 

#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