Skip to content

Commit

Permalink
TDD quiz (#29)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
hackeryarn authored and joncalhoun committed Jul 22, 2019
1 parent dae5550 commit 5ea0850
Show file tree
Hide file tree
Showing 8 changed files with 491 additions and 0 deletions.
Binary file added students/hackeryarn/hackeryarn
Binary file not shown.
120 changes: 120 additions & 0 deletions students/hackeryarn/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
package main

import (
"encoding/csv"
"flag"
"fmt"
"io"
"log"
"os"
"time"

quiz "github.com/gophercises/quiz/students/hackeryarn/myquiz"
"github.com/gophercises/quiz/students/hackeryarn/problem"
)

const (
// FileFlag is used to set a file for the questions
FileFlag = "file"
// FileFlagValue is the value used when no FileFlag is provided
FileFlagValue = "problems.csv"
// FileFlagUsage is the help string for the FileFlag
FileFlagUsage = "Questions file"

// TimerFlag is used for setting a timer for the quiz
TimerFlag = "timer"
// TimerFlagValue is the value used when no TimerFlag is provided
TimerFlagValue = 30
// TimerFlagUsage is the help string for the TimerFlag
TimerFlagUsage = "Amount of seconds the quiz will allow"
)

// Flagger configures the flags used
type Flagger interface {
StringVar(p *string, name, value, usage string)
IntVar(p *int, name string, value int, usage string)
}

type quizFlagger struct{}

func (q *quizFlagger) StringVar(p *string, name, value, usage string) {
flag.StringVar(p, name, value, usage)
}

func (q *quizFlagger) IntVar(p *int, name string, value int, usage string) {
flag.IntVar(p, name, value, usage)
}

// Timer is used to start a timer
type Timer interface {
NewTimer(d time.Duration) *time.Timer
}

type quizTimer struct{}

func (q quizTimer) NewTimer(d time.Duration) *time.Timer {
return time.NewTimer(d)
}

// ReadCSV parses the CSV file into a Problem struct
func ReadCSV(reader io.Reader) quiz.Quiz {
csvReader := csv.NewReader(reader)

problems := []problem.Problem{}
for {
record, err := csvReader.Read()
if err == io.EOF {
break
} else if err != nil {
log.Fatalln("Error reading CSV:", err)
}

problems = append(problems, problem.New(record))
}

return quiz.New(problems)
}

// TimerSeconds is the amount of time allowed for the quiz
var TimerSeconds int
var file string

// ConfigFlags sets all the flags used by the application
func ConfigFlags(f Flagger) {
f.StringVar(&file, FileFlag, FileFlagValue, FileFlagUsage)
f.IntVar(&TimerSeconds, TimerFlag, TimerFlagValue, TimerFlagUsage)
}

// StartTimer begins a timer once the user provides input
func StartTimer(w io.Writer, r io.Reader, timer Timer) *time.Timer {
fmt.Fprint(w, "Ready to start?")
fmt.Fscanln(r)

return timer.NewTimer(time.Second * time.Duration(TimerSeconds))
}

func init() {
flagger := &quizFlagger{}
ConfigFlags(flagger)

flag.Parse()
}

func main() {
file, err := os.Open(file)
if err != nil {
log.Fatalln("Could not open file", err)
}

quiz := ReadCSV(file)

timer := StartTimer(os.Stdout, os.Stdin, quizTimer{})
go func() {
<-timer.C
fmt.Println("")
quiz.PrintResults(os.Stdout)
os.Exit(0)
}()

quiz.Run(os.Stdout, os.Stdin)
}
40 changes: 40 additions & 0 deletions students/hackeryarn/myquiz/myquiz.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package quiz

import (
"fmt"
"io"

"github.com/gophercises/quiz/students/hackeryarn/problem"
)

// Quiz represents the quiz to be given to the user
type Quiz struct {
problems []problem.Problem
rightAnswers int
}

// Run runs the quiz for all the problems keeping track of correct answers
func (q *Quiz) Run(w io.Writer, r io.Reader) {
for _, problem := range q.problems {
problem.AskQuestion(w)
correct := problem.CheckAnswer(r)
if correct {
q.rightAnswers++
}
}

q.PrintResults(w)
}

// PrintResults outputs the results of the quiz
func (q Quiz) PrintResults(w io.Writer) {
fmt.Fprintf(w, "You got %d questions right!\n", q.rightAnswers)
}

// New creates a new quiz from the supplied slice of problems
func New(problems []problem.Problem) Quiz {
return Quiz{
problems: problems,
rightAnswers: 0,
}
}
66 changes: 66 additions & 0 deletions students/hackeryarn/myquiz/myquiz_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package quiz

import (
"bytes"
"io"
"reflect"
"strings"
"testing"

"github.com/gophercises/quiz/students/hackeryarn/problem"
)

func TestNew(t *testing.T) {
problems := sampleProblems()

want := Quiz{problems: problems, rightAnswers: 0}
got := New(problems)

if !reflect.DeepEqual(want, got) {
t.Errorf("expeted to create quiz %v got %v", want, got)
}
}

func TestRun(t *testing.T) {
t.Run("it runs the quiz", func(t *testing.T) {
buffer := &bytes.Buffer{}
quiz := createQuiz()
runQuiz(buffer, &quiz)

expectedResults := 2
results := quiz.rightAnswers

if expectedResults != results {
t.Errorf("expected right answers of %v, got %v",
expectedResults, results)
}

expectedOutput := "7+3: 1+1: You got 2 questions right!\n"

if buffer.String() != expectedOutput {
t.Errorf("expected full output %v, got %v",
expectedOutput, buffer)
}

})
}

func sampleProblems() []problem.Problem {
record1 := []string{"7+3", "10"}
record2 := []string{"1+1", "2"}

return []problem.Problem{
problem.New(record1),
problem.New(record2),
}
}

func createQuiz() Quiz {
problems := sampleProblems()
return New(problems)
}

func runQuiz(buffer io.Writer, quiz *Quiz) {
answers := strings.NewReader("10\n2\n")
quiz.Run(buffer, answers)
}
49 changes: 49 additions & 0 deletions students/hackeryarn/problem/problem.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package problem

import (
"fmt"
"io"
"log"
"strings"
)

// Problem represents a single question answer pair
type Problem struct {
question string
answer string
}

// CheckAnswer checks the answer against the provided input
func (p Problem) CheckAnswer(r io.Reader) bool {
answer := readAnswer(r)

if answer != p.answer {
return false
}
return true
}

func readAnswer(r io.Reader) (answer string) {
_, err := fmt.Fscanln(r, &answer)
if err != nil {
log.Fatalln("Error reading in answer", err)
}

return strings.TrimSpace(answer)
}

// AskQuestion prints out the question
func (p Problem) AskQuestion(w io.Writer) {
_, err := fmt.Fprintf(w, "%s: ", p.question)
if err != nil {
log.Fatalln("Could not ask the question", err)
}
}

// New creates a Problem from a provided CSV record
func New(record []string) Problem {
return Problem{
question: record[0],
answer: record[1],
}
}
68 changes: 68 additions & 0 deletions students/hackeryarn/problem/problem_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package problem

import (
"bytes"
"testing"
)

func TestNew(t *testing.T) {
record := []string{"question", "answer"}

want := Problem{"question", "answer"}
got := New(record)

if got != want {
t.Errorf("expected to create problem %v got %v", want, got)
}
}

func TestCheckAnswer(t *testing.T) {
problem := createProblem()

t.Run("it checks the correct answer", func(t *testing.T) {
answer := getAnswer(problem, "10\n")

checkAnswer(t, answer, true)
})

t.Run("it checks incorrect answer", func(t *testing.T) {
answer := getAnswer(problem, "2\n")

checkAnswer(t, answer, false)
})
}

func TestAskQuestion(t *testing.T) {
problem := createProblem()

t.Run("it asks the question", func(t *testing.T) {
buffer := bytes.NewBuffer(nil)

problem.AskQuestion(buffer)

want := "7+3: "
got := buffer.String()

if want != got {
t.Errorf("Expected question %s, got %s", want, got)
}
})

}

func createProblem() Problem {
record := []string{"7+3", "10"}
return New(record)
}

func getAnswer(problem Problem, input string) bool {
r := bytes.NewBufferString(input)

return problem.CheckAnswer(r)
}

func checkAnswer(t *testing.T, got, want bool) {
if want != got {
t.Errorf("Expected to return %v got %v", want, got)
}
}
12 changes: 12 additions & 0 deletions students/hackeryarn/problems.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
5+5,10
1+1,2
8+3,11
1+2,3
8+6,14
3+1,4
1+4,5
5+1,6
2+3,5
3+3,6
2+4,6
5+2,7
Loading

0 comments on commit 5ea0850

Please sign in to comment.