added Go variant

- using homegrown approval test function
This commit is contained in:
Christian Haas
2019-10-06 19:57:02 +02:00
parent 09a3047087
commit b7d9ed303b
10 changed files with 280 additions and 0 deletions

22
go/README.md Normal file
View File

@@ -0,0 +1,22 @@
## Theatrical-Players-Refactoring-Kata (Go)
This variant provides the kata in [Go](www.golang.org).
### Installation
* Install Go 1.12 (or later)
* In the directory of this file, run `go mod download`
* You can then run tests with `go test ./...`
### Approval Tests
Approval tests are run with a basic home-grown approval function (see `approval_test.go`).
In short, for a new test case of `TestPrinterPrintByApproval`:
* Add a new `.in.json` file under `testdata/TestPrinterPrintByApproval`. You can copy an existing one for starters.
* Run `go test ./...` - it should fail and complain about a missing file.
* However, a `.out.txt` file will have been created - use this one as the new `.approved.txt` file.
#### Approval-testing errors
Right now, the error is tested in-line. You can convert this test to an approval-test as well.

5
go/go.mod Normal file
View File

@@ -0,0 +1,5 @@
module github.com/emilybache/Theatrical-Players-Refactoring-Kata/go
go 1.13
require github.com/leekchan/accounting v0.0.0-20190702062627-a09595581342

10
go/go.sum Normal file
View File

@@ -0,0 +1,10 @@
github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I=
github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
github.com/leekchan/accounting v0.0.0-20190702062627-a09595581342 h1:vzuUv5azbeM31DiEL386i5eQESs5FNN2gDfkPowzNkk=
github.com/leekchan/accounting v0.0.0-20190702062627-a09595581342/go.mod h1:VfQkU+lPM8fjkGqiTn6R+4MX4A/pVwE1XGdDBqp0JFk=
github.com/lib/pq v1.0.0 h1:X5PMW56eZitiTeO7tKzZxFCSpbFZJtkMMooicw2us9A=
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24 h1:pntxY8Ary0t43dCZ5dqY4YTJCObLY1kIXl0uzMv+7DE=
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=

View File

@@ -0,0 +1,53 @@
package theatre
import (
"fmt"
"math"
"github.com/leekchan/accounting"
)
type StatementPrinter struct{}
func (StatementPrinter) Print(invoice Invoice, plays map[string]Play) (string, error) {
totalAmount := 0
volumeCredits := 0
result := fmt.Sprintf("Statement for %s\n", invoice.Customer)
ac := accounting.Accounting{Symbol: "$", Precision: 2}
for _, perf := range invoice.Performances {
play := plays[perf.PlayID]
thisAmount := 0
switch play.Type {
case "tragedy":
thisAmount = 40000
if perf.Audience > 30 {
thisAmount += 1000 * (perf.Audience - 30)
}
case "comedy":
thisAmount = 30000
if perf.Audience > 20 {
thisAmount += 10000 + 500*(perf.Audience-20)
}
thisAmount += 300 * perf.Audience
default:
return "", fmt.Errorf("unknown type: %s", play.Type)
}
// add volume credits
volumeCredits += int(math.Max(float64(perf.Audience)-30, 0))
// add extra credit for every ten comedy attendees
if play.Type == "comedy" {
volumeCredits += int(math.Floor(float64(perf.Audience) / 5))
}
// print line for this order
result += fmt.Sprintf(" %s: %s (%d seats)\n", play.Name, ac.FormatMoney(float64(thisAmount)/100), perf.Audience)
totalAmount += thisAmount
}
result += fmt.Sprintf("Amount owed is %s\n", ac.FormatMoney(float64(totalAmount)/100))
result += fmt.Sprintf("You earned %d credits\n", volumeCredits)
return result, nil
}

View File

@@ -0,0 +1,74 @@
package theatre_test
import (
"encoding/json"
"testing"
"github.com/emilybache/Theatrical-Players-Refactoring-Kata/go/theatre"
)
func TestPrinterPrintByApproval(t *testing.T) {
verify(t, "json", "txt", func(t testing.TB, data []byte) []byte {
var in struct {
Plays []struct {
ID string
Play struct {
Name string
Type string
}
}
Invoice struct {
Customer string
Performances []struct {
PlayID string
Audience int
}
}
}
if err := json.Unmarshal(data, &in); err != nil {
t.Fatalf("failed to unmarshal input data: %v", err)
return nil
}
// copy test-structure to production structure. Making use of matching types.
plays := make(map[string]theatre.Play)
invoice := theatre.Invoice{
Customer: in.Invoice.Customer,
Performances: make([]theatre.Performance, 0, len(in.Invoice.Performances)),
}
for _, perf := range in.Invoice.Performances {
invoice.Performances = append(invoice.Performances, perf)
}
for _, identifiedPlay := range in.Plays {
plays[identifiedPlay.ID] = identifiedPlay.Play
}
var printer theatre.StatementPrinter
statement, err := printer.Print(invoice, plays)
if err != nil {
t.Fatalf("failed to create statement, unexpected error: %v", err)
}
return []byte(statement)
})
}
func TestStatementWithNewPlayTypes(t *testing.T) {
plays := map[string]theatre.Play{
"henry-v": {Name: "Henry V", Type: "history"},
"as-like": {Name: "As You Like It", Type: "pastoral"},
}
invoice := theatre.Invoice{
Customer: "BigCo",
Performances: []theatre.Performance{
{PlayID: "henry-v", Audience: 53},
{PlayID: "as-like", Audience: 55},
},
}
var printer theatre.StatementPrinter
_, err := printer.Print(invoice, plays)
if err == nil {
t.Errorf("Expected an error, got none")
}
}

View File

@@ -0,0 +1,78 @@
package theatre_test
import (
"bytes"
"io/ioutil"
"path"
"strings"
"testing"
)
// verify runs a list of sub-tests using input data from the "testdata" subdirectory named after the current test.
// All files with the suffix ".in.<inType>" are used as input for the test case function.
// The name before the suffix is used as the base name for the files this function uses.
//
// What the function returns will be compared to the file with suffix ".approved.<outType>".
// If its content does not match the result, or the file does not exist, the test fails and a file with suffix ".out.<outType>" is created.
//
// In case the function encounters an error, it can call Fatalf() on the passed test instance.
// To verify errors as approved data, be sure to include the error in your structured data.
//
// This function reports problems of file access, as well as a failure if no input files were found.
func verify(t *testing.T, inType string, outType string, testCase func(testing.TB, []byte) []byte) {
t.Helper()
datadir := path.Join("testdata", t.Name())
files, err := ioutil.ReadDir(datadir)
if err != nil {
t.Fatalf("could not read test directory: %v", err)
}
fullInSuffix := ".in." + inType
fullApprovedSuffix := ".approved." + outType
inputData := make(map[string][]byte)
approvedData := make(map[string][]byte)
for _, file := range files {
switch {
case file.IsDir():
case strings.HasSuffix(file.Name(), fullInSuffix):
data, err := ioutil.ReadFile(path.Join(datadir, file.Name()))
if err != nil {
t.Errorf("failed to read input file %s: %v", file.Name(), err)
}
inputData[file.Name()] = data
case strings.HasSuffix(file.Name(), fullApprovedSuffix):
data, err := ioutil.ReadFile(path.Join(datadir, file.Name()))
if err != nil {
t.Errorf("failed to read approved file %s: %v", file.Name(), err)
}
approvedData[file.Name()] = data
}
}
if len(inputData) == 0 {
t.Fatalf("no input files in %s - is this the intent?", datadir)
}
for rangedInFilename, rangedInData := range inputData {
inFilename := rangedInFilename
inData := rangedInData
t.Run(t.Name()+"/"+inFilename, func(t *testing.T) {
result := testCase(t, inData)
baseFilename := inFilename[:len(inFilename)-len(fullInSuffix)]
expected, hasApproved := approvedData[baseFilename+fullApprovedSuffix]
writeOutFile := false
switch {
case !hasApproved:
t.Errorf("no approved data available (file %s not available)", baseFilename+fullApprovedSuffix)
writeOutFile = true
case !bytes.Equal(expected, result):
t.Errorf("result does not match expectation")
writeOutFile = true
}
if writeOutFile {
err = ioutil.WriteFile(path.Join(datadir, baseFilename+".out."+outType), result, 0644)
if err != nil {
t.Errorf("failed to write output data: %v", err)
}
}
})
}
}

1
go/theatre/testdata/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
*.out.*

View File

@@ -0,0 +1,6 @@
Statement for BigCo
Hamlet: $650.00 (55 seats)
As You Like It: $580.00 (35 seats)
Othello: $500.00 (40 seats)
Amount owed is $1,730.00
You earned 47 credits

View File

@@ -0,0 +1,15 @@
{
"plays": [
{ "id": "hamlet", "play": { "name": "Hamlet", "type": "tragedy" } },
{ "id": "as-like", "play": { "name": "As You Like It", "type": "comedy" } },
{ "id": "othello", "play": { "name": "Othello", "type": "tragedy" } }
],
"invoice": {
"customer": "BigCo",
"performances": [
{"playId": "hamlet", "audience": 55 },
{"playId": "as-like", "audience": 35 },
{"playId": "othello", "audience": 40 }
]
}
}

16
go/theatre/types.go Normal file
View File

@@ -0,0 +1,16 @@
package theatre
type Performance struct {
PlayID string
Audience int
}
type Play struct {
Name string
Type string
}
type Invoice struct {
Customer string
Performances []Performance
}