# 问题

svg 时钟

``````<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg"
width="100%"
height="100%"
viewBox="0 0 300 300"
version="2.0">

<!-- bezel -->
<circle cx="150" cy="150" r="100" style="fill:#fff;stroke:#000;stroke-width:5px;"/>

<!-- hour hand -->
<line x1="150" y1="150" x2="114.150000" y2="132.260000"
style="fill:none;stroke:#000;stroke-width:7px;"/>

<!-- minute hand -->
<line x1="150" y1="150" x2="101.290000" y2="99.730000"
style="fill:none;stroke:#000;stroke-width:7px;"/>

<!-- second hand -->
<line x1="150" y1="150" x2="77.190000" y2="202.900000"
style="fill:none;stroke:#f00;stroke-width:3px;"/>
</svg>``````

# 验收测试

``````<line x1="150" y1="150" x2="114.150000" y2="132.260000"
style="fill:none;stroke:#000;stroke-width:7px;"/>``````

• 时钟的圆心为 (150, 150)

• 时针长度 50

• 分针长度 80

• 秒针长度 90

SVG 需要注意的点：原点（0，0）—— 位于 左上角，而不是我们期望的 左下角。当我们计算线段长度时，一定要记住这点。

## 首先编写测试

``````package clockface_test

import (
"testing"
"time"

"github.com/gypsydave5/learn-go-with-tests/math/v1/clockface"
)

func TestSecondHandAtMidnight(t *testing.T) {
tm := time.Date(1337, time.January, 1, 0, 0, 0, 0, time.UTC)

want := clockface.Point{X: 150, Y: 150 - 90}
got := clockface.SecondHand(tm)

if got != want {
t.Errorf("Got %v, wanted %v", got, want)
}
}``````

## 尝试运行测试

``````--- FAIL: TestSecondHandAtMidnight (0.00s)
# github.com/gypsydave5/learn-go-with-tests/math/v1/clockface_test [github.com/gypsydave5/learn-go-with-tests/math/v1/clockface.test]
./clockface_test.go:13:10: undefined: clockface.Point
./clockface_test.go:14:9: undefined: clockface.SecondHand
FAIL    github.com/gypsydave5/learn-go-with-tests/math/v1/clockface [build failed]``````

## 编写最少的代码让测试跑起来并查看失败输出

``````package clockface

import "time"

// Point 表示二维笛卡尔坐标
type Point struct {
X float64
Y float64
}

// SecondHand 是指针式时钟在 `t` 时刻的秒针的单位矢量
// 表示一个 Point
func SecondHand(t time.Time) Point {
return Point{}
}``````

``````--- FAIL: TestSecondHandAtMidnight (0.00s)
clockface_test.go:17: Got {0 0}, wanted {150 60}
FAIL
exit status 1
FAIL    github.com/gypsydave5/learn-go-with-tests/math/v1/clockface    0.006s``````

## 编写足够的代码让测试通过

``````// SecondHand 是指针式时钟在 `t` 时刻的秒针的单位矢量
// 表示一个 Point
func SecondHand(t time.Time) Point {
return Point{150, 60}
}``````

``````PASS
ok      github.com/gypsydave5/learn-go-with-tests/math/v1/clockface    0.006s``````

## 首先编写测试

``````func TestSecondHandAt30Seconds(t *testing.T) {
tm := time.Date(1337, time.January, 1, 0, 0, 30, 0, time.UTC)

want := clockface.Point{X: 150, Y: 150 + 90}
got := clockface.SecondHand(tm)

if got != want {
t.Errorf("Got %v, wanted %v", got, want)
}
}``````

# 数学

(如果你不相信这个, 去看看维基百科)

# `math`

Cos 返回弧度 x 的余弦值。

## 首先编写测试

``````package clockface

import (
"math"
"testing"
"time"
)

thirtySeconds := time.Date(312, time.October, 28, 0, 0, 30, 0, time.UTC)
want := math.Pi

if want != got {
t.Fatalf("Wanted %v radians, but got %v", want, got)
}
}``````

## 尝试运行测试

``````# github.com/gypsydave5/learn-go-with-tests/math/v2/clockface [github.com/gypsydave5/learn-go-with-tests/math/v2/clockface.test]
FAIL    github.com/gypsydave5/learn-go-with-tests/math/v2/clockface [build failed]``````

## 编写最少量的代码让测试跑起来并检查失败输出

``````func secondsInRadians(t time.Time) float64 {
return 0
}``````
``````--- FAIL: TestSecondsInRadians (0.00s)
clockface_test.go:15: Wanted 3.141592653589793 radians, but got 0
FAIL
exit status 1
FAIL    github.com/gypsydave5/learn-go-with-tests/math/v2/clockface    0.007s``````

## 编写足够的代码让测试通过

``````func secondsInRadians(t time.Time) float64 {
return math.Pi
}``````
``````PASS
ok      github.com/gypsydave5/learn-go-with-tests/math/v2/clockface    0.011s``````

## 新需求

``````func TestSecondsInRadians(t *testing.T) {
cases := []struct {
time  time.Time
angle float64
}{
{simpleTime(0, 0, 30), math.Pi},
{simpleTime(0, 0, 0), 0},
{simpleTime(0, 0, 45), (math.Pi / 2) * 3},
{simpleTime(0, 0, 7), (math.Pi / 30) * 7},
}

for _, c := range cases {
t.Run(testName(c.time), func(t *testing.T) {
if got != c.angle {
t.Fatalf("Wanted %v radians, but got %v", c.angle, got)
}
})
}
}``````

``````func simpleTime(hours, minutes, seconds int) time.Time {
return time.Date(312, time.October, 28, hours, minutes, seconds, 0, time.UTC)
}

func testName(t time.Time) string {
return t.Format("15:04:05")
}``````

``````--- FAIL: TestSecondsInRadians (0.00s)
clockface_test.go:24: Wanted 0 radians, but got 3.141592653589793
clockface_test.go:24: Wanted 4.71238898038469 radians, but got 3.141592653589793
clockface_test.go:24: Wanted 0.7330382858376184 radians, but got 3.141592653589793
FAIL
exit status 1
FAIL    github.com/gypsydave5/learn-go-with-tests/math/v3/clockface    0.007s``````

``````func secondsInRadians(t time.Time) float64 {
return float64(t.Second()) * (math.Pi / 30)
}``````

``````--- FAIL: TestSecondsInRadians (0.00s)
clockface_test.go:24: Wanted 3.141592653589793 radians, but got 3.1415926535897936
FAIL
exit status 1
FAIL    github.com/gypsydave5/learn-go-with-tests/math/v3/clockface    0.006s``````

## 可怕的浮点数

1. 与之共存

2. 通过重构方程式来重构函数

``numberOfSeconds  *  π / 30``

``π / (30 / numberOfSeconds)``

``````func secondsInRadians(t time.Time) float64 {
return (math.Pi / (30 / (float64(t.Second()))))
}``````

``````PASS
ok      github.com/gypsydave5/learn-go-with-tests/math/v2/clockface     0.005s``````

## 首先编写测试

``````func TestSecondHandVector(t *testing.T) {
cases := []struct {
time  time.Time
point Point
}{
{simpleTime(0, 0, 30), Point{0, -1}},
}

for _, c := range cases {
t.Run(testName(c.time), func(t *testing.T) {
got := secondHandPoint(c.time)
if got != c.point {
t.Fatalf("Wanted %v Point, but got %v", c.point, got)
}
})
}
}``````

## 尝试运行测试

``````github.com/gypsydave5/learn-go-with-tests/math/v4/clockface [github.com/gypsydave5/learn-go-with-tests/math/v4/clockface.test]
./clockface_test.go:40:11: undefined: secondHandPoint
FAIL    github.com/gypsydave5/learn-go-with-tests/math/v4/clockface [build failed]``````

## 编写最少量的代码让测试跑起来并检查失败输出

``````func secondHandPoint(t time.Time) Point {
return Point{}
}``````
``````--- FAIL: TestSecondHandPoint (0.00s)
--- FAIL: TestSecondHandPoint/00:00:30 (0.00s)
clockface_test.go:42: Wanted {0 -1} Point, but got {0 0}
FAIL
exit status 1
FAIL    github.com/gypsydave5/learn-go-with-tests/math/v4/clockface    0.010s``````

## 编写足够的代码让测试通过

``````func secondHandPoint(t time.Time) Point {
return Point{0, -1}
}``````
``````PASS
ok      github.com/gypsydave5/learn-go-with-tests/math/v4/clockface    0.007s``````

## 新需求

``````func TestSecondHandPoint(t *testing.T) {
cases := []struct {
time  time.Time
point Point
}{
{simpleTime(0, 0, 30), Point{0, -1}},
{simpleTime(0, 0, 45), Point{-1, 0}},
}

for _, c := range cases {
t.Run(testName(c.time), func(t *testing.T) {
got := secondHandPoint(c.time)
if got != c.point {
t.Fatalf("Wanted %v Point, but got %v", c.point, got)
}
})
}
}``````

## 尝试运行测试

``````--- FAIL: TestSecondHandPoint (0.00s)
--- FAIL: TestSecondHandPoint/00:00:45 (0.00s)
clockface_test.go:43: Wanted {-1 0} Point, but got {0 -1}
FAIL
exit status 1
FAIL    github.com/gypsydave5/learn-go-with-tests/math/v4/clockface    0.006s``````

## 编写足够的代码让测试通过

``````func secondHandPoint(t time.Time) Point {
x := math.Sin(angle)
y := math.Cos(angle)

return Point{x, y}
}``````

``````--- FAIL: TestSecondHandPoint (0.00s)
--- FAIL: TestSecondHandPoint/00:00:30 (0.00s)
clockface_test.go:43: Wanted {0 -1} Point, but got {1.2246467991473515e-16 -1}
--- FAIL: TestSecondHandPoint/00:00:45 (0.00s)
clockface_test.go:43: Wanted {-1 0} Point, but got {-1 -1.8369701987210272e-16}
FAIL
exit status 1
FAIL    github.com/gypsydave5/learn-go-with-tests/math/v4/clockface    0.007s``````

``````func TestSecondHandPoint(t *testing.T) {
cases := []struct {
time  time.Time
point Point
}{
{simpleTime(0, 0, 30), Point{0, -1}},
{simpleTime(0, 0, 45), Point{-1, 0}},
}

for _, c := range cases {
t.Run(testName(c.time), func(t *testing.T) {
got := secondHandPoint(c.time)
if !roughlyEqualPoint(got, c.point) {
t.Fatalf("Wanted %v Point, but got %v", c.point, got)
}
})
}
}

func roughlyEqualFloat64(a, b float64) bool {
const equalityThreshold = 1e-7
return math.Abs(a-b) < equalityThreshold
}

func roughlyEqualPoint(a, b Point) bool {
return roughlyEqualFloat64(a.X, b.X) &&
roughlyEqualFloat64(a.Y, b.Y)
}``````

• 如果 X 和 Y 元素之间的距离不超过 0.0000001，它们将起作用。

这还是很准确的。

``````PASS
ok      github.com/gypsydave5/learn-go-with-tests/math/v4/clockface    0.007s``````

## 新需求

``````func TestSecondHandAt30Seconds(t *testing.T) {
tm := time.Date(1337, time.January, 1, 0, 0, 30, 0, time.UTC)

want := clockface.Point{X: 150, Y: 150 + 90}
got := clockface.SecondHand(tm)

if got != want {
t.Errorf("Got %v, wanted %v", got, want)
}
}``````

## 尝试运行测试

``````--- FAIL: TestSecondHandAt30Seconds (0.00s)
clockface_acceptance_test.go:28: Got {150 60}, wanted {150 240}
FAIL
exit status 1
FAIL    github.com/gypsydave5/learn-go-with-tests/math/v5/clockface    0.007s``````

## 编写足够的代码让测试通过

1.缩放到指针的长度

2.将其翻转到 X 轴上，因为要考虑到 SVG 的原点是左上角

3.将其平移到正确的位置(它来自于一个原点( 150,150 ))

``````// SecondHand 是指针式时钟在 `t` 时刻的秒针的单位矢量。
// 表示为 Point。
func SecondHand(t time.Time) Point {
p := secondHandPoint(t)
p = Point{p.X * 90, p.Y * 90}   // scale
p = Point{p.X, -p.Y}            // flip
p = Point{p.X + 150, p.Y + 150} // translate
return p
}``````

``````PASS
ok      github.com/gypsydave5/learn-go-with-tests/math/v5/clockface    0.007s``````

## 重构

``````const secondHandLength = 90
const clockCentreX = 150
const clockCentreY = 150

// SecondHand is the unit vector of the second hand of an analogue clock at time `t`
// represented as a Point.
func SecondHand(t time.Time) Point {
p := secondHandPoint(t)
p = Point{p.X * secondHandLength, p.Y * secondHandLength}
p = Point{p.X, -p.Y}
p = Point{p.X + clockCentreX, p.Y + clockCentreY} //translate
return p
}``````

# 画钟

``````├── clockface

│   └── main.go

├── clockface.go

├── clockface_acceptance_test.go

└── clockface_test.go``````

`main.go`

``````package main

import (
"fmt"
"io"
"os"
"time"

"github.com/gypsydave5/learn-go-with-tests/math/v6/clockface"
)

func main() {
t := time.Now()
sh := clockface.SecondHand(t)
io.WriteString(os.Stdout, svgStart)
io.WriteString(os.Stdout, bezel)
io.WriteString(os.Stdout, secondHandTag(sh))
io.WriteString(os.Stdout, svgEnd)
}

func secondHandTag(p clockface.Point) string {
return fmt.Sprintf(`<line x1="150" y1="150" x2="%f" y2="%f" style="fill:none;stroke:#f00;stroke-width:3px;"/>`, p.X, p.Y)
}

const svgStart = `<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg"
width="100%"
height="100%"
viewBox="0 0 300 300"
version="2.0">`

const bezel = `<circle cx="150" cy="150" r="100" style="fill:#fff;stroke:#000;stroke-width:5px;"/>`

const svgEnd = `</svg>```````

``go build``

``./clockface > clock.svg``

## 重构

1. 整个 `SecondHand` 函数是与 SVG 紧密 联系在一起的... 没有提到 SVG 或实际生成 SVG

2. ...同时，我没有测试任何 SVG 代码。

``````func TestSVGWriterAtMidnight(t *testing.T) {
tm := time.Date(1337, time.January, 1, 0, 0, 0, 0, time.UTC)

var b strings.Builder
clockface.SVGWriter(&b, tm)
got := b.String()

want := `<line x1="150" y1="150" x2="150" y2="60"`

if !strings.Contains(got, want) {
t.Errorf("Expected to find the second hand %v, in the SVG output %v", want, got)
}
}``````

# 解析 XML

`encoding/xml` 是一个可以处理所有与 XML 解析的 Go 包。

``````type Svg struct {
XMLName xml.Name `xml:"svg"`
Text    string   `xml:",chardata"`
Xmlns   string   `xml:"xmlns,attr"`
Width   string   `xml:"width,attr"`
Height  string   `xml:"height,attr"`
ViewBox string   `xml:"viewBox,attr"`
Version string   `xml:"version,attr"`
Circle  struct {
Text  string `xml:",chardata"`
Cx    string `xml:"cx,attr"`
Cy    string `xml:"cy,attr"`
R     string `xml:"r,attr"`
Style string `xml:"style,attr"`
} `xml:"circle"`
Line []struct {
Text  string `xml:",chardata"`
X1    string `xml:"x1,attr"`
Y1    string `xml:"y1,attr"`
X2    string `xml:"x2,attr"`
Y2    string `xml:"y2,attr"`
Style string `xml:"style,attr"`
} `xml:"line"`
}``````

``````func TestSVGWriterAtMidnight(t *testing.T) {
tm := time.Date(1337, time.January, 1, 0, 0, 0, 0, time.UTC)

b := bytes.Buffer{}
clockface.SVGWriter(&b, tm)

svg := Svg{}
xml.Unmarshal(b.Bytes(), &svg)

x2 := "150"
y2 := "60"

for _, line := range svg.Line {
if line.X2 == x2 && line.Y2 == y2 {
return
}
}

t.Errorf("Expected to find the second hand with x2 of %+v and y2 of %+v, in the SVG output %v", x2, y2, b.String())
}``````

``````# github.com/gypsydave5/learn-go-with-tests/math/v7b/clockface_test [github.com/gypsydave5/learn-go-with-tests/math/v7b/clockface.test]
./clockface_acceptance_test.go:41:2: undefined: clockface.SVGWriter
FAIL    github.com/gypsydave5/learn-go-with-tests/math/v7b/clockface [build failed]``````

``````package clockface

import (
"fmt"
"io"
"time"
)

const (
secondHandLength = 90
clockCentreX     = 150
clockCentreY     = 150
)

//SVGWriter writes an SVG representation of an analogue clock, showing the time t, to the writer w
func SVGWriter(w io.Writer, t time.Time) {
io.WriteString(w, svgStart)
io.WriteString(w, bezel)
secondHand(w, t)
io.WriteString(w, svgEnd)
}

func secondHand(w io.Writer, t time.Time) {
p := secondHandPoint(t)
p = Point{p.X * secondHandLength, p.Y * secondHandLength} // scale
p = Point{p.X, -p.Y}                                      // flip
p = Point{p.X + clockCentreX, p.Y + clockCentreY}         // translate
fmt.Fprintf(w, `<line x1="150" y1="150" x2="%.3f" y2="%.3f" style="fill:none;stroke:#f00;stroke-width:3px;"/>`, p.X, p.Y)
}

const svgStart = `<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg"
width="100%"
height="100%"
viewBox="0 0 300 300"
version="2.0">`

const bezel = `<circle cx="150" cy="150" r="100" style="fill:#fff;stroke:#000;stroke-width:5px;"/>`

const svgEnd = `</svg>```````

``````--- FAIL: TestSVGWriterAtMidnight (0.00s)
clockface_acceptance_test.go:56: Expected to find the second hand with x2 of 150 and y2 of 60, in the SVG output <?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg"
width="100%"
height="100%"
viewBox="0 0 300 300"
version="2.0"><circle cx="150" cy="150" r="100" style="fill:#fff;stroke:#000;stroke-width:5px;"/><line x1="150" y1="150" x2="150.000000" y2="60.000000" style="fill:none;stroke:#f00;stroke-width:3px;"/></svg>
FAIL
exit status 1
FAIL    github.com/gypsydave5/learn-go-with-tests/math/v7b/clockface    0.008s``````

``s := fmt.Sprintf(`<line x1="150" y1="150" x2="%.3f" y2="%.3f" style="fill:none;stroke:#f00;stroke-width:3px;"/>`, p.X, p.Y)``

`````` x2 :=  "150.000"
y2 :=  "60.000"``````

``````PASS
ok      github.com/gypsydave5/learn-go-with-tests/math/v7b/clockface    0.006s``````

``````package main

import (
"os"
"time"

"github.com/gypsydave5/learn-go-with-tests/math/v7b/clockface"
)

func main() {
t := time.Now()
clockface.SVGWriter(os.Stdout, t)
}``````

## 重构

1. 我们并没有真正测试我们需要确保存在的所有信息—例如，`x1` 值？

2. 另外，`x1` 它们的那些属性不是真的 `strings` 吗？他们是数字！

3. 我真的在乎指针的 `style` 吗? 或者，对于这个问题，是由 `zak` 生成的空 `Text` 节点?

``````type SVG struct {
XMLName xml.Name `xml:"svg"`
Xmlns   string   `xml:"xmlns,attr"`
Width   float64  `xml:"width,attr"`
Height  float64  `xml:"height,attr"`
ViewBox string   `xml:"viewBox,attr"`
Version string   `xml:"version,attr"`
Circle  Circle   `xml:"circle"`
Line    []Line   `xml:"line"`
}

type Circle struct {
Cx float64 `xml:"cx,attr"`
Cy float64 `xml:"cy,attr"`
R  float64 `xml:"r,attr"`
}

type Line struct {
X1 float64 `xml:"x1,attr"`
Y1 float64 `xml:"y1,attr"`
X2 float64 `xml:"x2,attr"`
Y2 float64 `xml:"y2,attr"`
}``````
• 将结构的重要部分命名为类型 -- `Line``Circle`

• 将数字属性转换为 `float64` 而不是 `string`

• 删除了未使用的属性，例如 `Style``Text`

• `Svg` 重命名为 `SVG` ，因为 这是正确的做法

``````func TestSVGWriterAtMidnight(t *testing.T) {
tm := time.Date(1337, time.January, 1, 0, 0, 0, 0, time.UTC)
b := bytes.Buffer{}

clockface.SVGWriter(&b, tm)

svg := SVG{}

xml.Unmarshal(b.Bytes(), &svg)

want := Line{150, 150, 150, 60}

for _, line := range svg.Line {
if line == want {
return
}
}

t.Errorf("Expected to find the second hand line %+v, in the SVG lines %+v", want, svg.Line)
}``````

``````func TestSVGWriterSecondHand(t *testing.T) {
cases := []struct {
time time.Time
line Line
}{
{
simpleTime(0, 0, 0),
Line{150, 150, 150, 60},
},
{
simpleTime(0, 0, 30),
Line{150, 150, 150, 240},
},
}

for _, c := range cases {
t.Run(testName(c.time), func(t *testing.T) {
b := bytes.Buffer{}
clockface.SVGWriter(&b, c.time)

svg := SVG{}
xml.Unmarshal(b.Bytes(), &svg)

if !containsLine(c.line, svg.Line) {
t.Errorf("Expected to find the second hand line %+v, in the SVG lines %+v", c.line, svg.Line)
}
})
}
}``````

## 首先编写测试

``````func TestSVGWriterMinutedHand(t *testing.T) {
cases := []struct {
time time.Time
line Line
}{
{
simpleTime(0, 0, 0),
Line{150, 150, 150, 70},
},
}

for _, c := range cases {
t.Run(testName(c.time), func(t *testing.T) {
b := bytes.Buffer{}
clockface.SVGWriter(&b, c.time)

svg := SVG{}
xml.Unmarshal(b.Bytes(), &svg)

if !containsLine(c.line, svg.Line) {
t.Errorf("Expected to find the minute hand line %+v, in the SVG lines %+v", c.line, svg.Line)
}
})
}
}``````

## 尝试运行测试

``````--- FAIL: TestSVGWriterMinutedHand (0.00s)
--- FAIL: TestSVGWriterMinutedHand/00:00:00 (0.00s)
clockface_acceptance_test.go:87: Expected to find the minute hand line {X1:150 Y1:150 X2:150 Y2:70}, in the SVG lines [{X1:150 Y1:150 X2:150 Y2:60}]
FAIL
exit status 1
FAIL    github.com/gypsydave5/learn-go-with-tests/math/v8/clockface    0.007s``````

``````func TestMinutesInRadians(t *testing.T) {
cases := []struct {
time  time.Time
angle float64
}{
{simpleTime(0, 30, 0), math.Pi},
}

for _, c := range cases {
t.Run(testName(c.time), func(t *testing.T) {
if got != c.angle {
t.Fatalf("Wanted %v radians, but got %v", c.angle, got)
}
})
}
}``````

## 尝试运行测试

``````# github.com/gypsydave5/learn-go-with-tests/math/v8/clockface [github.com/gypsydave5/learn-go-with-tests/math/v8/clockface.test]
FAIL    github.com/gypsydave5/learn-go-with-tests/math/v8/clockface [build failed]``````

## 编写最少量的代码让测试跑起来，并

``````func minutesInRadians(t time.Time) float64 {
return math.Pi
}``````

## 新需求

``````func TestMinutesInRadians(t *testing.T) {
cases := []struct {
time  time.Time
angle float64
}{
{simpleTime(0, 30, 0), math.Pi},
{simpleTime(0, 0, 7), 7 * (math.Pi / (30 * 60))},
}

for _, c := range cases {
t.Run(testName(c.time), func(t *testing.T) {
if got != c.angle {
t.Fatalf("Wanted %v radians, but got %v", c.angle, got)
}
})
}
}``````

• 60 秒是 1 分钟

• 半圈就是半小时 (`math.Pi` 弧度)

• 所以 `30 * 60` 秒是半圈

• 如果时间是 1 小时过 7 秒 ...

• ... 我们期望 `7 * (math.Pi / (30 * 60))` 分针超过 12 点的弧度

## 尝试运行测试

``````--- FAIL: TestMinutesInRadians (0.00s)
clockface_test.go:62: Wanted 0.012217304763960306 radians, but got 3.141592653589793
FAIL
exit status 1
FAIL    github.com/gypsydave5/learn-go-with-tests/math/v8/clockface    0.009s``````

## 编写足够的代码让测试通过

``````func minutesInRadians(t time.Time) float64 {
(math.Pi / (30 / float64(t.Minute())))
}``````

``secondsInRadians(t)  /  60``

``math.Pi /  (30  /  float64(t.Minute()))``

``````PASS

ok      github.com/gypsydave5/learn-go-with-tests/math/v8/clockface 0.007s``````

## 首先编写测试

``````func TestMinuteHandPoint(t *testing.T) {
cases := []struct {
time  time.Time
point Point
}{
{simpleTime(0, 30, 0), Point{0, -1}},
}

for _, c := range cases {
t.Run(testName(c.time), func(t *testing.T) {
got := minuteHandPoint(c.time)
if !roughlyEqualPoint(got, c.point) {
t.Fatalf("Wanted %v Point, but got %v", c.point, got)
}
})
}
}``````

## 尝试运行测试

``````# github.com/gypsydave5/learn-go-with-tests/math/v9/clockface [github.com/gypsydave5/learn-go-with-tests/math/v9/clockface.test]
./clockface_test.go:79:11: undefined: minuteHandPoint
FAIL    github.com/gypsydave5/learn-go-with-tests/math/v9/clockface [build failed]``````

## 编写最少量的代码让测试跑起来并查看失败输出

``````func minuteHandPoint(t time.Time) Point {
return Point{}
}``````
``````--- FAIL: TestMinuteHandPoint (0.00s)
--- FAIL: TestMinuteHandPoint/00:30:00 (0.00s)
clockface_test.go:80: Wanted {0 -1} Point, but got {0 0}
FAIL
exit status 1
FAIL    github.com/gypsydave5/learn-go-with-tests/math/v9/clockface    0.007s``````

## 编写足够的代码让测试通过

``````func minuteHandPoint(t time.Time) Point {
return Point{0, -1}
}``````
``````PASS

ok      github.com/gypsydave5/learn-go-with-tests/math/v9/clockface    0.007s``````

## 新需求

``````func TestMinuteHandPoint(t *testing.T) {
cases := []struct {
time  time.Time
point Point
}{
{simpleTime(0, 30, 0), Point{0, -1}},
{simpleTime(0, 45, 0), Point{-1, 0}},
}

for _, c := range cases {
t.Run(testName(c.time), func(t *testing.T) {
got := minuteHandPoint(c.time)
if !roughlyEqualPoint(got, c.point) {
t.Fatalf("Wanted %v Point, but got %v", c.point, got)
}
})
}
}``````
``````--- FAIL: TestMinuteHandPoint (0.00s)
--- FAIL: TestMinuteHandPoint/00:45:00 (0.00s)
clockface_test.go:81: Wanted {-1 0} Point, but got {0 -1}
FAIL
exit status 1
FAIL    github.com/gypsydave5/learn-go-with-tests/math/v9/clockface    0.007s``````

## 编写足够的代码让测试通过

``````func minuteHandPoint(t time.Time) Point {
x := math.Sin(angle)
y := math.Cos(angle)

return Point{x, y}
}``````
``````PASS

ok      github.com/gypsydave5/learn-go-with-tests/math/v9/clockface    0.009s``````

## 重构

``````func angleToPoint(angle float64) Point {
x := math.Sin(angle)
y := math.Cos(angle)

return Point{x, y}
}``````

``````func minuteHandPoint(t time.Time) Point {
}``````
``````func secondHandPoint(t time.Time) Point {
}``````
``````PASS

ok      github.com/gypsydave5/learn-go-with-tests/math/v9/clockface    0.007s``````

## 编写足够的代码让测试通过

``````func minuteHand(w io.Writer, t time.Time) {
p := minuteHandPoint(t)
p = Point{p.X * minuteHandLength, p.Y * minuteHandLength}
p = Point{p.X, -p.Y}
p = Point{p.X + clockCentreX, p.Y + clockCentreY}
fmt.Fprintf(w, `<line x1="150" y1="150" x2="%.3f" y2="%.3f" style="fill:none;stroke:#000;stroke-width:3px;"/>`, p.X, p.Y)
}``````
``````PASS

ok      github.com/gypsydave5/learn-go-with-tests/math/v9/clockface    0.006s``````

## 重构

``````func secondHand(w io.Writer, t time.Time) {
p := makeHand(secondHandPoint(t), secondHandLength)
fmt.Fprintf(w, `<line x1="150" y1="150" x2="%.3f" y2="%.3f" style="fill:none;stroke:#f00;stroke-width:3px;"/>`, p.X, p.Y)
}

func minuteHand(w io.Writer, t time.Time) {
p := makeHand(minuteHandPoint(t), minuteHandLength)
fmt.Fprintf(w, `<line x1="150" y1="150" x2="%.3f" y2="%.3f" style="fill:none;stroke:#000;stroke-width:3px;"/>`, p.X, p.Y)
}

func makeHand(p Point, length float64) Point {
p = Point{p.X * length, p.Y * length}
p = Point{p.X, -p.Y}
return Point{p.X + clockCentreX, p.Y + clockCentreY}
}``````
``````PASS

ok      github.com/gypsydave5/learn-go-with-tests/math/v9/clockface    0.007s``````

## 首先编写测试

``````func TestSVGWriterHourHand(t *testing.T) {
cases := []struct {
time time.Time
line Line
}{
{
simpleTime(6, 0, 0),
Line{150, 150, 150, 200},
},
}

for _, c := range cases {
t.Run(testName(c.time), func(t *testing.T) {
b := bytes.Buffer{}
clockface.SVGWriter(&b, c.time)

svg := SVG{}
xml.Unmarshal(b.Bytes(), &svg)

if !containsLine(c.line, svg.Line) {
t.Errorf("Expected to find the hour hand line %+v, in the SVG lines %+v", c.line, svg.Line)
}
})
}
}``````

## 尝试运行测试

``````--- FAIL: TestSVGWriterHourHand (0.00s)
--- FAIL: TestSVGWriterHourHand/06:00:00 (0.00s)
clockface_acceptance_test.go:113: Expected to find the hour hand line {X1:150 Y1:150 X2:150 Y2:200}, in the SVG lines [{X1:150 Y1:150 X2:150 Y2:60} {X1:150 Y1:150 X2:150 Y2:70}]
FAIL
exit status 1
FAIL    github.com/gypsydave5/learn-go-with-tests/math/v10/clockface    0.013s``````

## 首先编写测试

``````func TestHoursInRadians(t *testing.T) {
cases := []struct {
time  time.Time
angle float64
}{
{simpleTime(6, 0, 0), math.Pi},
}

for _, c := range cases {
t.Run(testName(c.time), func(t *testing.T) {
if got != c.angle {
t.Fatalf("Wanted %v radians, but got %v", c.angle, got)
}
})
}
}``````

## 尝试运行测试

``````# github.com/gypsydave5/learn-go-with-tests/math/v10/clockface [github.com/gypsydave5/learn-go-with-tests/math/v10/clockface.test]
FAIL    github.com/gypsydave5/learn-go-with-tests/math/v10/clockface [build failed]``````

## 编写最少量的代码让测试跑起来并检查失败输出

``````func hoursInRadians(t time.Time) float64 {
return math.Pi
}``````
``````PASS
ok      github.com/gypsydave5/learn-go-with-tests/math/v10/clockface    0.007s``````

## 新需求

``````func TestHoursInRadians(t *testing.T) {
cases := []struct {
time  time.Time
angle float64
}{
{simpleTime(6, 0, 0), math.Pi},
{simpleTime(0, 0, 0), 0},
}

for _, c := range cases {
t.Run(testName(c.time), func(t *testing.T) {
if got != c.angle {
t.Fatalf("Wanted %v radians, but got %v", c.angle, got)
}
})
}
}``````

## 尝试运行测试

``````--- FAIL: TestHoursInRadians (0.00s)
clockface_test.go:100: Wanted 0 radians, but got 3.141592653589793
FAIL
exit status 1
FAIL    github.com/gypsydave5/learn-go-with-tests/math/v10/clockface    0.007s``````

## 编写足够的代码让测试通过

``````func hoursInRadians(t time.Time) float64 {
return (math.Pi / (6 / float64(t.Hour())))
}``````

## 新需求

``````func TestHoursInRadians(t *testing.T) {
cases := []struct {
time  time.Time
angle float64
}{
{simpleTime(6, 0, 0), math.Pi},
{simpleTime(0, 0, 0), 0},
{simpleTime(21, 0, 0), math.Pi * 1.5},
}

for _, c := range cases {
t.Run(testName(c.time), func(t *testing.T) {
if got != c.angle {
t.Fatalf("Wanted %v radians, but got %v", c.angle, got)
}
})
}
}``````

## 尝试运行测试

``````--- FAIL: TestHoursInRadians (0.00s)
clockface_test.go:101: Wanted 4.71238898038469 radians, but got 10.995574287564276
FAIL
exit status 1
FAIL    github.com/gypsydave5/learn-go-with-tests/math/v10/clockface    0.014s``````

## 编写足够的代码让测试通过

``````func hoursInRadians(t time.Time) float64 {
return (math.Pi / (6 / (float64(t.Hour() % 12))))
}``````

``````PASS
ok      github.com/gypsydave5/learn-go-with-tests/math/v10/clockface    0.008s``````

## 首先编写测试

``````func TestHoursInRadians(t *testing.T) {
cases := []struct {
time  time.Time
angle float64
}{
{simpleTime(6, 0, 0), math.Pi},
{simpleTime(0, 0, 0), 0},
{simpleTime(21, 0, 0), math.Pi * 1.5},
{simpleTime(0, 1, 30), math.Pi / ((6 * 60 * 60) / 90)},
}

for _, c := range cases {
t.Run(testName(c.time), func(t *testing.T) {
if got != c.angle {
t.Fatalf("Wanted %v radians, but got %v", c.angle, got)
}
})
}
}``````

## 尝试运行测试

``````--- FAIL: TestHoursInRadians (0.00s)
clockface_test.go:102: Wanted 0.013089969389957472 radians, but got 0
FAIL
exit status 1
FAIL    github.com/gypsydave5/learn-go-with-tests/math/v10/clockface    0.007s``````

## 编写足够的代码让测试通过

``````func hoursInRadians(t time.Time) float64 {
(math.Pi / (6 / float64(t.Hour()%12)))
}``````

``````--- FAIL: TestHoursInRadians (0.00s)
clockface_test.go:104: Wanted 0.013089969389957472 radians, but got 0.01308996938995747
FAIL
exit status 1
FAIL    github.com/gypsydave5/learn-go-with-tests/math/v10/clockface    0.007s``````

``````func TestHoursInRadians(t *testing.T) {
cases := []struct {
time  time.Time
angle float64
}{
{simpleTime(6, 0, 0), math.Pi},
{simpleTime(0, 0, 0), 0},
{simpleTime(21, 0, 0), math.Pi * 1.5},
{simpleTime(0, 1, 30), math.Pi / ((6 * 60 * 60) / 90)},
}

for _, c := range cases {
t.Run(testName(c.time), func(t *testing.T) {
if !roughlyEqualFloat64(got, c.angle) {
t.Fatalf("Wanted %v radians, but got %v", c.angle, got)
}
})
}
}``````
``````PASS

ok      github.com/gypsydave5/learn-go-with-tests/math/v10/clockface    0.007s``````

# 时针

## 首先编写测试

``````func TestHourHandPoint(t *testing.T) {
cases := []struct {
time  time.Time
point Point
}{
{simpleTime(6, 0, 0), Point{0, -1}},
{simpleTime(21, 0, 0), Point{-1, 0}},
}

for _, c := range cases {
t.Run(testName(c.time), func(t *testing.T) {
got := hourHandPoint(c.time)
if !roughlyEqualPoint(got, c.point) {
t.Fatalf("Wanted %v Point, but got %v", c.point, got)
}
})
}
}``````

## 尝试运行测试

``````# github.com/gypsydave5/learn-go-with-tests/math/v11/clockface [github.com/gypsydave5/learn-go-with-tests/math/v11/clockface.test]
./clockface_test.go:119:11: undefined: hourHandPoint
FAIL    github.com/gypsydave5/learn-go-with-tests/math/v11/clockface [build failed]``````

## 编写足够的代码让测试通过

``````func  hourHandPoint(t time.Time) Point {

}``````

``````PASS

ok      github.com/gypsydave5/learn-go-with-tests/math/v11/clockface    0.009s``````

# 绘制时针

``````func  TestSVGWriterHourHand(t *testing.T)  {

cases :=  []struct  {

time time.Time

line Line
for  _, c :=  range cases {

t.Run(testName(c.time),  func(t *testing.T)  {

b := bytes.Buffer{}
}``````

## 尝试运行测试

``````--- FAIL: TestSVGWriterHourHand (0.00s)
--- FAIL: TestSVGWriterHourHand/06:00:00 (0.00s)
clockface_acceptance_test.go:113: Expected to find the hour hand line {X1:150 Y1:150 X2:150 Y2:200}, in the SVG lines [{X1:150 Y1:150 X2:150 Y2:60} {X1:150 Y1:150 X2:150 Y2:70}]
FAIL
exit status 1
FAIL    github.com/gypsydave5/learn-go-with-tests/math/v10/clockface    0.013s``````

## 编写足够的代码让测试通过

``````const (
secondHandLength = 90
minuteHandLength = 80
hourHandLength   = 50
clockCentreX     = 150
clockCentreY     = 150
)

//SVGWriter writes an SVG representation of an analogue clock, showing the time t, to the writer w
func SVGWriter(w io.Writer, t time.Time) {
io.WriteString(w, svgStart)
io.WriteString(w, bezel)
secondHand(w, t)
minuteHand(w, t)
hourHand(w, t)
io.WriteString(w, svgEnd)
}

// ...

func hourHand(w io.Writer, t time.Time) {
p := makeHand(hourHandPoint(t), hourHandLength)
fmt.Fprintf(w, `<line x1="150" y1="150" x2="%.3f" y2="%.3f" style="fill:none;stroke:#000;stroke-width:3px;"/>`, p.X, p.Y)
}``````

``````PASS
ok      github.com/gypsydave5/learn-go-with-tests/math/v12/clockface    0.007s``````

## 重构

``````const (
secondsInHalfClock = 30
secondsInClock     = 2 * secondsInHalfClock
minutesInHalfClock = 30
minutesInClock     = 2 * minutesInHalfClock
hoursInHalfClock   = 6
hoursInClock       = 2 * hoursInHalfClock
)``````

# 结束

## 一个程序... 和一个库

API 应该与程序一起提供，反之亦然。必须编写 C 代码才能使用的 API 很难从命令行中轻松调用，因此更难学习和使用。相反，如果接口的惟一开放的、文档化的形式是程序，那么就很难从 C 程序中调用它们。-- Henry Spencer, 在 Unix 编程的艺术

## 最有价值的测试

• 使用 `text/template` 中的模版?

• 使用 XML 库 (就像我们测试中所做的一样)?

• 使用 SVG 库?