Membuat Testing Untuk Stdin dan Stdout

Masih melanjutkan dari modul sebelumnya, kita akan ubah program agar bisa menerima user input.

Buka file main.go, lalu ubah seperti berikut.

package main

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

func main() {
	// welcome message
	intro()

	//channel untuk quit
	doneChan := make(chan bool)

	//jalankan go routine untuk membaca user input
	go readUserInput(os.Stdin, doneChan)

	//block sampai doneChan mendapatkan value
	<-doneChan

	//close channel
	close(doneChan)

	fmt.Println("Bye...")
}

func readUserInput(in io.Reader, doneChan chan bool) {
	scanner := bufio.NewScanner(in)
	for {
		res, done := checkNumbers(scanner)

		if done {
			doneChan <- true
			return
		}

		fmt.Println(res)
	}
}

func checkNumbers(scanner *bufio.Scanner) (string, bool) {
	//read user input
	scanner.Scan()

	//check user quit
	if strings.EqualFold(scanner.Text(), "q") {
		return "", true
	}

	numToCheck, err := strconv.Atoi(scanner.Text())
	if err != nil {
		return "Enter whole number", false
	}

	_, msg := isPrime(numToCheck)
	return msg, false

}

func intro() {
	fmt.Println("Prime Number")
	fmt.Println("------------")
	fmt.Println("Enter whole number (q for quit).")
	myPrompt()
}

func myPrompt() {
	fmt.Print("-> ")
}

func isPrime(n int) (bool, string) {
	if n == 0 || n == 1 {
		return false, fmt.Sprintf("%d is not prime", n)
	}

	if n < 0 {
		return false, "negative is not prime"
	}

	for i := 2; i <= n/2; i++ {
		if n%i == 0 {
			return false, fmt.Sprintf("%d is not prime because is divisible by %d", n, i)
		}
	}

	return true, fmt.Sprintf("%d is prime number", n)
}

Pertama kita akan membuat Test untuk fungsi prompt().

Buka file main_test.go, lalu tambahkan code dibawah (lihat baris komentar untuk penjelasan code).

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

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

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

	myPrompt()

	//close writer
	_ = w.Close()

	//reset os.Stdout
	os.Stdout = oldOut

	//read output dari myPrompt() func
	out, _ := io.ReadAll(r)

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

}

Selanjutnya adalah membuat test untuk fungsi intro(). Tambahkan code berikut.

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

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

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

	myPrompt()

	//close writer
	_ = w.Close()

	//reset os.Stdout
	os.Stdout = oldOut

	//read output dari myPrompt() func
	out, _ := io.ReadAll(r)

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

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

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

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

	intro()

	//close writer
	_ = w.Close()

	//reset os.Stdout
	os.Stdout = oldOut

	//read output dari myPrompt() func
	out, _ := io.ReadAll(r)

	//perform test
	if !strings.Contains(string(out), "Enter whole number") {
		t.Errorf("Incorrect intro text, got %s", string(out))
	}
}

Kemudian tambahkan test untuk fungsi checkNumbers().

func Test_checkNumbers(t *testing.T) {
	tests := []struct {
		name     string
		input    string
		expected string
	}{
		{"empty", "", "Enter whole number"},
		{"zero", "0", "0 is not prime"},
		{"one", "1", "1 is not prime"},
		{"two", "2", "2 is prime number"},
		{"four", "4", "4 is not prime because is divisible by 2"},
		{"negative", "-2", "negative is not prime"},
		{"decimal", "4.0", "Enter whole number"},
		{"quit", "q", ""},
	}

	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, but got %s", e.expected, res)
		}
	}
}

Fungsi terakhir yang perlu dibuat test adalah readUserInput(), gunakan kode berikut untuk fungsi testing Test_readUserInput().

func Test_readUserInput(t *testing.T) {
	//untuk test fungsi ini, diperlukan channel dan io.Reader instance
	doneChan := make(chan bool)

	//reference ke bytes.Buffer
	var stdin bytes.Buffer

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

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

Berikut isi lengkap dari main_test.go

package main

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

func Test_isPrime(t *testing.T) {
	primeTests := []struct {
		name     string
		testNum  int
		expected bool
		msg      string
	}{
		{"prime", 7, true, "7 is prime number"},
		{"not prime", 4, false, "4 is not prime because is divisible by 2"},
		{"zero", 0, false, "0 is not prime"},
		{"one", 1, false, "1 is not prime"},
		{"negatif", -1, false, "negative is not prime"},
	}

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

		if e.expected && !result {
			t.Errorf("%s: expected true but got false", e.name)
		}

		if !e.expected && result {
			t.Errorf("%s: expected false but got true", e.name)
		}

		if e.msg != msg {
			t.Errorf("%s: expected %s but got %s", e.name, e.msg, msg)
		}
	}
}

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

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

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

	myPrompt()

	//close writer
	_ = w.Close()

	//reset os.Stdout
	os.Stdout = oldOut

	//read output dari myPrompt() func
	out, _ := io.ReadAll(r)

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

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

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

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

	intro()

	//close writer
	_ = w.Close()

	//reset os.Stdout
	os.Stdout = oldOut

	//read output dari myPrompt() func
	out, _ := io.ReadAll(r)

	//perform test
	if !strings.Contains(string(out), "Enter whole number") {
		t.Errorf("Incorrect intro text, got %s", string(out))
	}
}

func Test_checkNumbers(t *testing.T) {
	tests := []struct {
		name     string
		input    string
		expected string
	}{
		{"empty", "", "Enter whole number"},
		{"zero", "0", "0 is not prime"},
		{"one", "1", "1 is not prime"},
		{"two", "2", "2 is prime number"},
		{"four", "4", "4 is not prime because is divisible by 2"},
		{"negative", "-2", "negative is not prime"},
		{"decimal", "4.0", "Enter whole number"},
		{"quit", "q", ""},
	}

	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, but got %s", e.expected, res)
		}
	}
}

func Test_readUserInput(t *testing.T) {
	//untuk test fungsi ini, diperlukan channel dan io.Reader instance
	doneChan := make(chan bool)

	//reference ke bytes.Buffer
	var stdin bytes.Buffer

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

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

Jika Anda periksa menggunakan coverage, maka seluruh fungsi sudah berhasil ditest.

$  go test -cover .

ok      primeapp        (cached)        coverage: 82.4% of statements

Kita bisa lihat juga report coverage profile

$ go test -coverprofile=coverage
$ go tool cover -html=coverage

Sampai disini kita sudah membahas membuat test untuk fungsi yang menggunakan input dan output.

Sharing is caring:

Leave a Comment