mirror of
https://github.com/ysoftdevs/Theatrical-Players-Refactoring-Kata.git
synced 2026-01-11 22:30:27 +01:00
added Go variant
- using homegrown approval test function
This commit is contained in:
22
go/README.md
Normal file
22
go/README.md
Normal 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
5
go/go.mod
Normal 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
10
go/go.sum
Normal 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=
|
||||
53
go/theatre/StatementPrinter.go
Normal file
53
go/theatre/StatementPrinter.go
Normal 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
|
||||
}
|
||||
74
go/theatre/StatementPrinter_test.go
Normal file
74
go/theatre/StatementPrinter_test.go
Normal 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")
|
||||
}
|
||||
}
|
||||
78
go/theatre/approval_test.go
Normal file
78
go/theatre/approval_test.go
Normal 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
1
go/theatre/testdata/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
*.out.*
|
||||
6
go/theatre/testdata/TestPrinterPrintByApproval/1.approved.txt
vendored
Normal file
6
go/theatre/testdata/TestPrinterPrintByApproval/1.approved.txt
vendored
Normal 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
|
||||
15
go/theatre/testdata/TestPrinterPrintByApproval/1.in.json
vendored
Normal file
15
go/theatre/testdata/TestPrinterPrintByApproval/1.in.json
vendored
Normal 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
16
go/theatre/types.go
Normal 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
|
||||
}
|
||||
Reference in New Issue
Block a user