Skip to content

Commit 5ea0850

Browse files
hackeryarnjoncalhoun
authored andcommitted
TDD quiz (#29)
* reading csv * refactored problem to it's own module * new quiz test * checks answer * started tests for asking questions * running quiz * supporting flags * able to set timer * start timer * fully running quiz * renamed quiz to myquiz
1 parent dae5550 commit 5ea0850

File tree

8 files changed

+491
-0
lines changed

8 files changed

+491
-0
lines changed

students/hackeryarn/hackeryarn

2.29 MB
Binary file not shown.

students/hackeryarn/main.go

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
package main
2+
3+
import (
4+
"encoding/csv"
5+
"flag"
6+
"fmt"
7+
"io"
8+
"log"
9+
"os"
10+
"time"
11+
12+
quiz "github.com/gophercises/quiz/students/hackeryarn/myquiz"
13+
"github.com/gophercises/quiz/students/hackeryarn/problem"
14+
)
15+
16+
const (
17+
// FileFlag is used to set a file for the questions
18+
FileFlag = "file"
19+
// FileFlagValue is the value used when no FileFlag is provided
20+
FileFlagValue = "problems.csv"
21+
// FileFlagUsage is the help string for the FileFlag
22+
FileFlagUsage = "Questions file"
23+
24+
// TimerFlag is used for setting a timer for the quiz
25+
TimerFlag = "timer"
26+
// TimerFlagValue is the value used when no TimerFlag is provided
27+
TimerFlagValue = 30
28+
// TimerFlagUsage is the help string for the TimerFlag
29+
TimerFlagUsage = "Amount of seconds the quiz will allow"
30+
)
31+
32+
// Flagger configures the flags used
33+
type Flagger interface {
34+
StringVar(p *string, name, value, usage string)
35+
IntVar(p *int, name string, value int, usage string)
36+
}
37+
38+
type quizFlagger struct{}
39+
40+
func (q *quizFlagger) StringVar(p *string, name, value, usage string) {
41+
flag.StringVar(p, name, value, usage)
42+
}
43+
44+
func (q *quizFlagger) IntVar(p *int, name string, value int, usage string) {
45+
flag.IntVar(p, name, value, usage)
46+
}
47+
48+
// Timer is used to start a timer
49+
type Timer interface {
50+
NewTimer(d time.Duration) *time.Timer
51+
}
52+
53+
type quizTimer struct{}
54+
55+
func (q quizTimer) NewTimer(d time.Duration) *time.Timer {
56+
return time.NewTimer(d)
57+
}
58+
59+
// ReadCSV parses the CSV file into a Problem struct
60+
func ReadCSV(reader io.Reader) quiz.Quiz {
61+
csvReader := csv.NewReader(reader)
62+
63+
problems := []problem.Problem{}
64+
for {
65+
record, err := csvReader.Read()
66+
if err == io.EOF {
67+
break
68+
} else if err != nil {
69+
log.Fatalln("Error reading CSV:", err)
70+
}
71+
72+
problems = append(problems, problem.New(record))
73+
}
74+
75+
return quiz.New(problems)
76+
}
77+
78+
// TimerSeconds is the amount of time allowed for the quiz
79+
var TimerSeconds int
80+
var file string
81+
82+
// ConfigFlags sets all the flags used by the application
83+
func ConfigFlags(f Flagger) {
84+
f.StringVar(&file, FileFlag, FileFlagValue, FileFlagUsage)
85+
f.IntVar(&TimerSeconds, TimerFlag, TimerFlagValue, TimerFlagUsage)
86+
}
87+
88+
// StartTimer begins a timer once the user provides input
89+
func StartTimer(w io.Writer, r io.Reader, timer Timer) *time.Timer {
90+
fmt.Fprint(w, "Ready to start?")
91+
fmt.Fscanln(r)
92+
93+
return timer.NewTimer(time.Second * time.Duration(TimerSeconds))
94+
}
95+
96+
func init() {
97+
flagger := &quizFlagger{}
98+
ConfigFlags(flagger)
99+
100+
flag.Parse()
101+
}
102+
103+
func main() {
104+
file, err := os.Open(file)
105+
if err != nil {
106+
log.Fatalln("Could not open file", err)
107+
}
108+
109+
quiz := ReadCSV(file)
110+
111+
timer := StartTimer(os.Stdout, os.Stdin, quizTimer{})
112+
go func() {
113+
<-timer.C
114+
fmt.Println("")
115+
quiz.PrintResults(os.Stdout)
116+
os.Exit(0)
117+
}()
118+
119+
quiz.Run(os.Stdout, os.Stdin)
120+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package quiz
2+
3+
import (
4+
"fmt"
5+
"io"
6+
7+
"github.com/gophercises/quiz/students/hackeryarn/problem"
8+
)
9+
10+
// Quiz represents the quiz to be given to the user
11+
type Quiz struct {
12+
problems []problem.Problem
13+
rightAnswers int
14+
}
15+
16+
// Run runs the quiz for all the problems keeping track of correct answers
17+
func (q *Quiz) Run(w io.Writer, r io.Reader) {
18+
for _, problem := range q.problems {
19+
problem.AskQuestion(w)
20+
correct := problem.CheckAnswer(r)
21+
if correct {
22+
q.rightAnswers++
23+
}
24+
}
25+
26+
q.PrintResults(w)
27+
}
28+
29+
// PrintResults outputs the results of the quiz
30+
func (q Quiz) PrintResults(w io.Writer) {
31+
fmt.Fprintf(w, "You got %d questions right!\n", q.rightAnswers)
32+
}
33+
34+
// New creates a new quiz from the supplied slice of problems
35+
func New(problems []problem.Problem) Quiz {
36+
return Quiz{
37+
problems: problems,
38+
rightAnswers: 0,
39+
}
40+
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
package quiz
2+
3+
import (
4+
"bytes"
5+
"io"
6+
"reflect"
7+
"strings"
8+
"testing"
9+
10+
"github.com/gophercises/quiz/students/hackeryarn/problem"
11+
)
12+
13+
func TestNew(t *testing.T) {
14+
problems := sampleProblems()
15+
16+
want := Quiz{problems: problems, rightAnswers: 0}
17+
got := New(problems)
18+
19+
if !reflect.DeepEqual(want, got) {
20+
t.Errorf("expeted to create quiz %v got %v", want, got)
21+
}
22+
}
23+
24+
func TestRun(t *testing.T) {
25+
t.Run("it runs the quiz", func(t *testing.T) {
26+
buffer := &bytes.Buffer{}
27+
quiz := createQuiz()
28+
runQuiz(buffer, &quiz)
29+
30+
expectedResults := 2
31+
results := quiz.rightAnswers
32+
33+
if expectedResults != results {
34+
t.Errorf("expected right answers of %v, got %v",
35+
expectedResults, results)
36+
}
37+
38+
expectedOutput := "7+3: 1+1: You got 2 questions right!\n"
39+
40+
if buffer.String() != expectedOutput {
41+
t.Errorf("expected full output %v, got %v",
42+
expectedOutput, buffer)
43+
}
44+
45+
})
46+
}
47+
48+
func sampleProblems() []problem.Problem {
49+
record1 := []string{"7+3", "10"}
50+
record2 := []string{"1+1", "2"}
51+
52+
return []problem.Problem{
53+
problem.New(record1),
54+
problem.New(record2),
55+
}
56+
}
57+
58+
func createQuiz() Quiz {
59+
problems := sampleProblems()
60+
return New(problems)
61+
}
62+
63+
func runQuiz(buffer io.Writer, quiz *Quiz) {
64+
answers := strings.NewReader("10\n2\n")
65+
quiz.Run(buffer, answers)
66+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package problem
2+
3+
import (
4+
"fmt"
5+
"io"
6+
"log"
7+
"strings"
8+
)
9+
10+
// Problem represents a single question answer pair
11+
type Problem struct {
12+
question string
13+
answer string
14+
}
15+
16+
// CheckAnswer checks the answer against the provided input
17+
func (p Problem) CheckAnswer(r io.Reader) bool {
18+
answer := readAnswer(r)
19+
20+
if answer != p.answer {
21+
return false
22+
}
23+
return true
24+
}
25+
26+
func readAnswer(r io.Reader) (answer string) {
27+
_, err := fmt.Fscanln(r, &answer)
28+
if err != nil {
29+
log.Fatalln("Error reading in answer", err)
30+
}
31+
32+
return strings.TrimSpace(answer)
33+
}
34+
35+
// AskQuestion prints out the question
36+
func (p Problem) AskQuestion(w io.Writer) {
37+
_, err := fmt.Fprintf(w, "%s: ", p.question)
38+
if err != nil {
39+
log.Fatalln("Could not ask the question", err)
40+
}
41+
}
42+
43+
// New creates a Problem from a provided CSV record
44+
func New(record []string) Problem {
45+
return Problem{
46+
question: record[0],
47+
answer: record[1],
48+
}
49+
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
package problem
2+
3+
import (
4+
"bytes"
5+
"testing"
6+
)
7+
8+
func TestNew(t *testing.T) {
9+
record := []string{"question", "answer"}
10+
11+
want := Problem{"question", "answer"}
12+
got := New(record)
13+
14+
if got != want {
15+
t.Errorf("expected to create problem %v got %v", want, got)
16+
}
17+
}
18+
19+
func TestCheckAnswer(t *testing.T) {
20+
problem := createProblem()
21+
22+
t.Run("it checks the correct answer", func(t *testing.T) {
23+
answer := getAnswer(problem, "10\n")
24+
25+
checkAnswer(t, answer, true)
26+
})
27+
28+
t.Run("it checks incorrect answer", func(t *testing.T) {
29+
answer := getAnswer(problem, "2\n")
30+
31+
checkAnswer(t, answer, false)
32+
})
33+
}
34+
35+
func TestAskQuestion(t *testing.T) {
36+
problem := createProblem()
37+
38+
t.Run("it asks the question", func(t *testing.T) {
39+
buffer := bytes.NewBuffer(nil)
40+
41+
problem.AskQuestion(buffer)
42+
43+
want := "7+3: "
44+
got := buffer.String()
45+
46+
if want != got {
47+
t.Errorf("Expected question %s, got %s", want, got)
48+
}
49+
})
50+
51+
}
52+
53+
func createProblem() Problem {
54+
record := []string{"7+3", "10"}
55+
return New(record)
56+
}
57+
58+
func getAnswer(problem Problem, input string) bool {
59+
r := bytes.NewBufferString(input)
60+
61+
return problem.CheckAnswer(r)
62+
}
63+
64+
func checkAnswer(t *testing.T, got, want bool) {
65+
if want != got {
66+
t.Errorf("Expected to return %v got %v", want, got)
67+
}
68+
}

students/hackeryarn/problems.csv

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
5+5,10
2+
1+1,2
3+
8+3,11
4+
1+2,3
5+
8+6,14
6+
3+1,4
7+
1+4,5
8+
5+1,6
9+
2+3,5
10+
3+3,6
11+
2+4,6
12+
5+2,7

0 commit comments

Comments
 (0)