From 3686956a024c5c0d015820b61f969616850022f9 Mon Sep 17 00:00:00 2001
From: Drew Bednar <drew@runcible.io>
Date: Thu, 28 Mar 2024 15:22:59 -0400
Subject: [PATCH] Adding structs and interfaces

---
 learn_go_with_tests/structs/README.md      |  7 ++
 learn_go_with_tests/structs/go.mod         |  3 +
 learn_go_with_tests/structs/shapes.go      | 49 ++++++++++++++
 learn_go_with_tests/structs/shapes_test.go | 74 ++++++++++++++++++++++
 4 files changed, 133 insertions(+)
 create mode 100644 learn_go_with_tests/structs/README.md
 create mode 100644 learn_go_with_tests/structs/go.mod
 create mode 100644 learn_go_with_tests/structs/shapes.go
 create mode 100644 learn_go_with_tests/structs/shapes_test.go

diff --git a/learn_go_with_tests/structs/README.md b/learn_go_with_tests/structs/README.md
new file mode 100644
index 0000000..b695dce
--- /dev/null
+++ b/learn_go_with_tests/structs/README.md
@@ -0,0 +1,7 @@
+# Testing Structs
+
+You can run individual tests like:
+
+```
+go test -run TestArea/Rectangle
+```
diff --git a/learn_go_with_tests/structs/go.mod b/learn_go_with_tests/structs/go.mod
new file mode 100644
index 0000000..e9ce053
--- /dev/null
+++ b/learn_go_with_tests/structs/go.mod
@@ -0,0 +1,3 @@
+module structs
+
+go 1.21.0
diff --git a/learn_go_with_tests/structs/shapes.go b/learn_go_with_tests/structs/shapes.go
new file mode 100644
index 0000000..1b4e029
--- /dev/null
+++ b/learn_go_with_tests/structs/shapes.go
@@ -0,0 +1,49 @@
+package structs
+
+import "math"
+
+// In Go interface resolution is implicit. So Rectangle and Cirle have
+// receiver functions that satisfy the Shape interface.
+type Shape interface {
+	Area() float64
+}
+
+type Rectangle struct {
+	Width  float64
+	Height float64
+}
+
+type Circle struct {
+	Radius float64
+}
+
+type Triangle struct {
+	Base   float64
+	Height float64
+}
+
+// There are examples of functions (not methods)
+// Perimeter calculates the perimeter of a rectangle
+func Perimeter(rectangle Rectangle) float64 {
+	return 2 * (rectangle.Width + rectangle.Height)
+}
+
+// Area calculates the area of a rectangle
+func Area(rectangle Rectangle) float64 {
+	return rectangle.Width * rectangle.Height
+}
+
+// A method is a function with a receiver. A method declaration binds an identifier,
+// the method name, to a method, and associates the method with the receiver's base type.
+func (r Rectangle) Area() float64 {
+	return r.Height * r.Width
+}
+
+func (c Circle) Area() float64 {
+	return math.Pow(c.Radius, 2) * math.Pi
+}
+
+func (t Triangle) Area() float64 {
+	// return (t.Base * t.Height) / 2
+	return (t.Base * t.Height) * 0.5
+}
diff --git a/learn_go_with_tests/structs/shapes_test.go b/learn_go_with_tests/structs/shapes_test.go
new file mode 100644
index 0000000..08ba043
--- /dev/null
+++ b/learn_go_with_tests/structs/shapes_test.go
@@ -0,0 +1,74 @@
+package structs
+
+import "testing"
+
+func TestPerimeter(t *testing.T) {
+	rectangle := Rectangle{10.0, 10.0}
+	got := Perimeter(rectangle)
+	want := 40.0
+
+	if got != want {
+		t.Errorf("got %.2f want %.2f", got, want)
+	}
+}
+
+// %.2f is replaced with %g which will print a more precise decimal number in an error message.
+
+func TestArea(t *testing.T) {
+
+	// Helper function
+	checkArea := func(t testing.TB, shape Shape, want float64) {
+		t.Helper()
+		got := shape.Area()
+		if got != want {
+			t.Errorf("got %g want %g", got, want)
+		}
+	}
+
+	t.Run("area rectangle", func(t *testing.T) {
+		rectangle := Rectangle{12.0, 6.0}
+		checkArea(t, rectangle, 72.0)
+	})
+
+	t.Run("area circle", func(t *testing.T) {
+		circle := Circle{10}
+		checkArea(t, circle, 314.1592653589793)
+	})
+
+}
+
+// https://go.dev/wiki/TableDrivenTests are useful when you want to build a list of test cases that can be tested in the same manner.
+// They are a great fit when you wish to test various implementations of an interface, or if the data being passed in to a function has lots of different requirements that need testing.
+func TestAreaTable(t *testing.T) {
+
+	// "anonymous struct"
+	areaTests := []struct {
+		name  string
+		shape Shape
+		want  float64
+	}{
+		// example showing named arguments. Arguably it is more readable.
+		{name: "Rectangle", shape: Rectangle{Width: 12, Height: 6}, want: 72.0},
+		{"Circle", Circle{10}, 314.1592653589793},
+		{"Triangle", Triangle{12, 6}, 36.0},
+	}
+
+	// for _, tt := range areaTests {
+	// 	got := tt.shape.Area()
+	// 	if got != tt.want {
+	// 		// %#v format string will print out our struct with the values in its field
+	// 		t.Errorf("%#v got %g want %g", tt, got, tt.want)
+	// 	}
+	//}
+
+	// Wrap in t.Run for better debug output and test failure identification
+	for _, tt := range areaTests {
+		t.Run(tt.name, func(t *testing.T) {
+			got := tt.shape.Area()
+			if got != tt.want {
+				t.Errorf("%#v got %g want %g", tt, got, tt.want)
+			}
+		})
+	}
+
+}