Search code examples
unit-testinggouser-input

Testing a Function by Passing Input Multiple Times


I'm learning Go with tests. I ended up with the below program from the end of chapter 2 in "Head First Go".

It gives the user three attempts to guess a number from 1 to 10:

guess.go

package main

import (
    "fmt"
    "math/rand"
    "time"
    "bufio"
    "os"
    "log"
    "strconv"
    "strings"
)

func generateRandomNumber() int {
    rand.Seed(time.Now().Unix())       // seed value based on time to generate non-deterministic random values
    randomNum := rand.Intn(10) + 1     // range: [0, 10); produces the same value without Seed(), i.e., pseudo-random
    return randomNum
}

func guess() int {
    var success bool = false
    target := generateRandomNumber()

    fmt.Println("Guess a number between 1 and 10")
    guess  := bufio.NewReader(os.Stdin)

    for attempts := 0; attempts < 3; attempts++ {
        fmt.Println("You have", 3-attempts, "guesses left")
        userVal, err := guess.ReadString('\n')
        if err != nil {
            log.Fatal(err)
        }
    
        input := strings.TrimSpace(userVal)
        answer, err := strconv.Atoi(input)
        if err != nil {
            log.Fatal(err)
        }
    
        if answer == target {
            fmt.Println("Congratulations !!")
            return answer
        } else if answer > target {
            fmt.Println("Your guess was HIGHER")
        } else if answer < target {
            fmt.Println("Your guess was LOWER")
        }
    }

    if !success {
        fmt.Printf("Sorry, you've run out of attempts... The correct value is %d\n", target)
        return target   
    }
    return 0
}

func main() {
    guess()
}

guess_test.go

package main

import (
    "testing"
)

func TestRandomNumber(t *testing.T) {
    want := generateRandomNumber()
    
    if 7 != want {
        t.Fail()
        t.Logf("Incorrect guess; The random number was %d", want)
    }
}

How can I test guess() by passing in three different inputs?

I would like to perform a test by comparing the return value of guess() with generateRandomNumber().


Solution

  • You can change guess function to get reader from input, by this we able to pass it any reader we want: in main we pass stdin reader and in test we pass mock reader:

    guess.go

    package main
    
    import (
        "bufio"
        "fmt"
        "log"
        "math/rand"
        "os"
        "strconv"
        "strings"
        "time"
    )
    
    func generateRandomNumber() int {
        rand.Seed(time.Now().Unix())   // seed value based on time to generate non-deterministic random values
        randomNum := rand.Intn(10) + 1 // range: [0, 10); produces the same value without Seed(), i.e., pseudo-random
        return randomNum
    }
    
    func guess(reader *bufio.Reader) (int, error) {
        target := generateRandomNumber()
    
        fmt.Println("Guess a number between 1 and 10")
    
        for attempts := 0; attempts < 3; attempts++ {
            fmt.Println("You have", 3-attempts, "guesses left")
            userVal, err := reader.ReadString('\n')
            if err != nil {
                log.Fatal(err)
            }
    
            input := strings.TrimSpace(userVal)
            answer, err := strconv.Atoi(input)
            if err != nil {
                log.Fatal(err)
            }
    
            if answer == target {
                fmt.Println("Congratulations !!")
                return answer, nil
            } else if answer > target {
                fmt.Println("Your guess was HIGHER")
            } else if answer < target {
                fmt.Println("Your guess was LOWER")
            }
        }
    
        fmt.Printf("Sorry, you've run out of attempts... The correct value is %d\n", target)
        return target, fmt.Errorf("attempts is over")
    }
    
    func main() {
        reader := bufio.NewReader(os.Stdin)
        guess(reader)
    }
    

    for testing:

    guess_test.go

    package main
    
    import (
        "bufio"
        "fmt"
        "strings"
        "testing"
    )
    
    func TestRandomNumberOk(t *testing.T) {
        want := generateRandomNumber()
        msg := fmt.Sprintf("3\n4\n%d\n", want)
        reader := strings.NewReader(msg)
        r := bufio.NewReader(reader)
        _, err := guess(r)
        if err != nil {
            t.Errorf("guess must successfull")
        }
    }
    
    func TestRandomNumberFail(t *testing.T) {
        want := generateRandomNumber()
        msg := fmt.Sprintf("3\n4\n%d\n", want+1)
        reader := strings.NewReader(msg)
        r := bufio.NewReader(reader)
        _, err := guess(r)
        if err == nil {
            t.Errorf("guess must unsuccessfull")
        }
    }
    

    I had to change your guess return value, because it's unknown when it's successful or not