From fb1b643ca8500cabaaff4006f8c3f057e9578353 Mon Sep 17 00:00:00 2001 From: Arseny Balobanov Date: Sat, 21 Mar 2020 02:56:58 +0300 Subject: [PATCH 1/7] Adding testequal task. --- testequal/README.md | 41 ++++++++++++ testequal/helper_test.go | 40 ++++++++++++ testequal/require.go | 31 +++++++++ testequal/require_test.go | 129 ++++++++++++++++++++++++++++++++++++++ testequal/t.go | 10 +++ 5 files changed, 251 insertions(+) create mode 100644 testequal/README.md create mode 100644 testequal/helper_test.go create mode 100644 testequal/require.go create mode 100644 testequal/require_test.go create mode 100644 testequal/t.go diff --git a/testequal/README.md b/testequal/README.md new file mode 100644 index 0000000..8c4193a --- /dev/null +++ b/testequal/README.md @@ -0,0 +1,41 @@ +## testequal + +В этой задаче нужно реализовать 4 test helper'а аналогичных функциям из [testify](https://github.com/stretchr/testify): + +``` +func AssertEqual(t T, expected, actual interface{}, msgAndArgs ...interface{}) bool +func AssertNotEqual(t T, expected, actual interface{}, msgAndArgs ...interface{}) +func RequireEqual(t T, expected, actual interface{}, msgAndArgs ...interface{}) +func RequireNotEqual(t T, expected, actual interface{}, msgAndArgs ...interface{}) +``` + +Пример теста: +``` +func TestMath(t *testing.T) { + AssertEqual(t, 1, 2, "1 == 2") +} +``` +вывод: +``` +=== RUN TestMath +--- FAIL: TestMath (0.00s) + helper_test.go:43: not equal: + expected: 1 + actual : 2 + message : 1 == 2 +FAIL +FAIL gitlab.com/slon/shad-go/require 0.003s +FAIL +``` + +В отличие от testify реализация ограничивает набор типов, с которыми умеет работать: +1. Целые числа: int, in64 и др (см. тесты) +2. string +3. map[string]string +4. []int +5. []byte + +## Ссылки + +1. testing.T: https://golang.org/pkg/testing/#T +2. type assertions: https://golang.org/doc/effective_go.html#interface_conversions diff --git a/testequal/helper_test.go b/testequal/helper_test.go new file mode 100644 index 0000000..5b203a6 --- /dev/null +++ b/testequal/helper_test.go @@ -0,0 +1,40 @@ +package testequal + +import ( + "bytes" + "os" + "os/exec" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestHelper(t *testing.T) { + if os.Getenv("FAIL_ASSERTIONS") == "1" { + AssertEqual(t, 1, 2, "%d must be equal to %d", 1, 2) + AssertNotEqual(t, 1, 1, "1 != 1") + RequireEqual(t, 1, 2, errMsgLog{Exp: 1, Act: 2}) + return + } + + cmd := exec.Command(os.Args[0], "-test.v", "-test.run=TestHelper") + cmd.Env = append(os.Environ(), "FAIL_ASSERTIONS=1") + var buf bytes.Buffer + cmd.Stdout = &buf + + err := cmd.Run() + if e, ok := err.(*exec.ExitError); ok && !e.Success() { + require.Contains(t, buf.String(), "helper_test.go:14") + require.Contains(t, buf.String(), "1 must be equal to 2") + require.Contains(t, buf.String(), "helper_test.go:15") + require.Contains(t, buf.String(), "1 != 1") + require.Contains(t, buf.String(), "helper_test.go:16") + return + } + t.Fatalf("process ran with err %v, want exit status 1", err) +} + +type errMsgLog struct { + Exp int + Act int +} diff --git a/testequal/require.go b/testequal/require.go new file mode 100644 index 0000000..477cd90 --- /dev/null +++ b/testequal/require.go @@ -0,0 +1,31 @@ +// +build !solution + +package testequal + +// AssertEqual checks that expected and actual are equal. +// +// Marks caller function as having failed but continues execution. +// +// Returns true iff arguments are equal. +func AssertEqual(t T, expected, actual interface{}, msgAndArgs ...interface{}) bool { + panic("implement me") +} + +// AssertNotEqual checks that expected and actual are not equal. +// +// Marks caller function as having failed but continues execution. +// +// Returns true iff arguments are not equal. +func AssertNotEqual(t T, expected, actual interface{}, msgAndArgs ...interface{}) bool { + panic("implement me") +} + +// RequireEqual does the same as AssertEqual but fails caller test immediately. +func RequireEqual(t T, expected, actual interface{}, msgAndArgs ...interface{}) { + panic("implement me") +} + +// RequireNotEqual does the same as AssertNotEqual but fails caller test immediately. +func RequireNotEqual(t T, expected, actual interface{}, msgAndArgs ...interface{}) { + panic("implement me") +} diff --git a/testequal/require_test.go b/testequal/require_test.go new file mode 100644 index 0000000..340433a --- /dev/null +++ b/testequal/require_test.go @@ -0,0 +1,129 @@ +package testequal + +import ( + "fmt" + "math" + "strings" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestEqual(t *testing.T) { + for _, tc := range []struct { + name string + expected, actual interface{} + }{ + {name: "int", expected: 1, actual: 1}, + {name: "int8", expected: int8(1), actual: int8(1)}, + {name: "int16", expected: int16(1), actual: int16(1)}, + {name: "int32", expected: int32(1), actual: int32(1)}, + {name: "int64", expected: int64(1), actual: int64(1)}, + {name: "uint8", expected: uint8(1), actual: uint8(1)}, + {name: "uint16", expected: uint16(1), actual: uint16(1)}, + {name: "uint32", expected: uint32(1), actual: uint32(1)}, + {name: "uint64", expected: uint64(1), actual: uint64(1)}, + {name: "string", expected: 1, actual: 1}, + {name: "slice", expected: []int{1, 2, 3}, actual: []int{1, 2, 3}}, + {name: "map", expected: map[string]string{"a": "b"}, actual: map[string]string{"a": "b"}}, + {name: "bytes", expected: []byte(`abc`), actual: []byte(`abc`)}, + } { + t.Run(tc.name, func(t *testing.T) { + AssertEqual(t, tc.expected, tc.actual) + RequireEqual(t, tc.expected, tc.actual) + }) + } +} + +func TestNotEqual(t *testing.T) { + for _, tc := range []struct { + expected, actual interface{} + }{ + {expected: 1, actual: uint(1)}, + {expected: uint(1), actual: int8(1)}, + {expected: int8(1), actual: uint8(1)}, + {expected: uint8(1), actual: int16(1)}, + {expected: int16(1), actual: uint16(1)}, + {expected: uint16(1), actual: int32(1)}, + {expected: int32(1), actual: uint32(1)}, + {expected: uint32(1), actual: int64(1)}, + {expected: int64(1), actual: uint64(1)}, + {expected: uint64(1), actual: 1}, + {expected: int32(32), actual: uint32(32)}, + {expected: int64(0), actual: 0}, + {expected: 0, actual: int64(0)}, + {expected: 123, actual: []int{123}}, + {expected: 123, actual: map[string]string{}}, + {expected: 123, actual: nil}, + {expected: math.MaxInt64, actual: math.MaxInt32}, + {expected: []int{}, actual: nil}, + {expected: []int{}, actual: nil}, + {expected: []int{1, 2, 3}, actual: []int{}}, + {expected: []int{1, 2, 3}, actual: []int{1, 3, 3}}, + {expected: []int{1, 2, 3}, actual: []int{1, 2, 3, 4}}, + {expected: []int{1, 2, 3, 4}, actual: []int{1, 2, 3}}, + {expected: []int{}, actual: []interface{}{}}, + {expected: []int{}, actual: *new([]int)}, + {expected: map[string]string{"a": "b"}, actual: map[string]string{}}, + {expected: map[string]string{"a": "b"}, actual: map[string]string{"a": "d"}}, + {expected: map[string]string{"a": "b"}, actual: map[string]string{"a": "b", "c": "b"}}, + {expected: map[string]string{"a": "b", "c": "b"}, actual: map[string]string{"a": "b"}}, + {expected: map[string]string{"a": "b"}, actual: map[string]interface{}{"a": "b"}}, + {expected: map[string]string{}, actual: *new(map[string]string)}, + {expected: []byte{}, actual: *new([]byte)}, + {expected: []byte{}, actual: nil}, + {expected: *new([]byte), actual: nil}, + {expected: struct{}{}, actual: struct{}{}}, // unsupported type + } { + t.Run(fmt.Sprintf("%T_%T", tc.expected, tc.actual), func(t *testing.T) { + AssertNotEqual(t, tc.expected, tc.actual) + RequireNotEqual(t, tc.expected, tc.actual) + }) + } +} + +type mockT struct{} + +func (m *mockT) Errorf(format string, args ...interface{}) {} + +func (m *mockT) FailNow() {} + +func (m *mockT) Helper() {} + +func BenchmarkRequireEqual(b *testing.B) { + t := &mockT{} + b.ResetTimer() + for i := 0; i < b.N; i++ { + RequireEqual(t, int64(1), int64(1)) + } +} + +func BenchmarkTestifyRequireEqual(b *testing.B) { + t := &mockT{} + b.ResetTimer() + for i := 0; i < b.N; i++ { + require.Equal(t, int64(1), int64(1)) + } +} + +func BenchmarkRequireBytesEqual(b *testing.B) { + s1 := strings.Repeat("abacaba", 1024) + s2 := strings.Repeat("abacaba", 1024) + + mockT := &mockT{} + b.ResetTimer() + for i := 0; i < b.N; i++ { + RequireEqual(mockT, s1, s2) + } +} + +func BenchmarkTestifyRequireStringsEqual(b *testing.B) { + s1 := strings.Repeat("abacaba", 1024*8) + s2 := strings.Repeat("abacaba", 1024*8) + + mockT := &mockT{} + b.ResetTimer() + for i := 0; i < b.N; i++ { + require.Equal(mockT, s1, s2) + } +} diff --git a/testequal/t.go b/testequal/t.go new file mode 100644 index 0000000..2bf598e --- /dev/null +++ b/testequal/t.go @@ -0,0 +1,10 @@ +// +build !change + +package testequal + +// T is an interface wrapper around *testing.T. +type T interface { + Errorf(format string, args ...interface{}) + Helper() + FailNow() +} From 063b5e976498b4ffa487719caab9e9415826634e Mon Sep 17 00:00:00 2001 From: Arseny Balobanov Date: Sat, 21 Mar 2020 03:04:24 +0300 Subject: [PATCH 2/7] Fix benchmark name. --- testequal/require_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testequal/require_test.go b/testequal/require_test.go index 340433a..dacce60 100644 --- a/testequal/require_test.go +++ b/testequal/require_test.go @@ -106,7 +106,7 @@ func BenchmarkTestifyRequireEqual(b *testing.B) { } } -func BenchmarkRequireBytesEqual(b *testing.B) { +func BenchmarkRequireStringsEqual(b *testing.B) { s1 := strings.Repeat("abacaba", 1024) s2 := strings.Repeat("abacaba", 1024) From 39c638aa2fd15d98111c7b0385552b61220edcbc Mon Sep 17 00:00:00 2001 From: Arseny Balobanov Date: Sat, 21 Mar 2020 03:12:06 +0300 Subject: [PATCH 3/7] Fix readme. --- testequal/README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/testequal/README.md b/testequal/README.md index 8c4193a..054cddc 100644 --- a/testequal/README.md +++ b/testequal/README.md @@ -1,6 +1,6 @@ ## testequal -В этой задаче нужно реализовать 4 test helper'а аналогичных функциям из [testify](https://github.com/stretchr/testify): +В этой задаче нужно реализовать 4 test helper'а, аналогичных функциям из [testify](https://github.com/stretchr/testify): ``` func AssertEqual(t T, expected, actual interface{}, msgAndArgs ...interface{}) bool @@ -9,22 +9,22 @@ func RequireEqual(t T, expected, actual interface{}, msgAndArgs ...interface{}) func RequireNotEqual(t T, expected, actual interface{}, msgAndArgs ...interface{}) ``` -Пример теста: +Пример использования: ``` func TestMath(t *testing.T) { AssertEqual(t, 1, 2, "1 == 2") } ``` -вывод: +вывод теста: ``` === RUN TestMath --- FAIL: TestMath (0.00s) - helper_test.go:43: not equal: + math_test.go:43: not equal: expected: 1 actual : 2 message : 1 == 2 FAIL -FAIL gitlab.com/slon/shad-go/require 0.003s +FAIL gitlab.com/slon/shad-go/testequal 0.003s FAIL ``` From 69abab266ccdfccb398d8ec4491cf6e371bcd32f Mon Sep 17 00:00:00 2001 From: Arseny Balobanov Date: Sat, 21 Mar 2020 03:32:54 +0300 Subject: [PATCH 4/7] Add note about err msg. --- testequal/README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/testequal/README.md b/testequal/README.md index 054cddc..0177755 100644 --- a/testequal/README.md +++ b/testequal/README.md @@ -9,6 +9,9 @@ func RequireEqual(t T, expected, actual interface{}, msgAndArgs ...interface{}) func RequireNotEqual(t T, expected, actual interface{}, msgAndArgs ...interface{}) ``` +Функции проверяют на равенство expected и actual и завершают тест, если проверка не прошла. +msgAndArgs попадают в описание ошибки через fmt.Sprintf. + Пример использования: ``` func TestMath(t *testing.T) { From 3fe2f3144fedb176729846c0c84c68bbfda5b2cd Mon Sep 17 00:00:00 2001 From: Arseny Balobanov Date: Sat, 21 Mar 2020 03:50:02 +0300 Subject: [PATCH 5/7] Fix string benchmark size; rename benchmarks; add map benchmark. --- testequal/require_test.go | 34 ++++++++++++++++++++++++++++------ 1 file changed, 28 insertions(+), 6 deletions(-) diff --git a/testequal/require_test.go b/testequal/require_test.go index dacce60..9eef970 100644 --- a/testequal/require_test.go +++ b/testequal/require_test.go @@ -90,7 +90,7 @@ func (m *mockT) FailNow() {} func (m *mockT) Helper() {} -func BenchmarkRequireEqual(b *testing.B) { +func BenchmarkRequireEqualInt64(b *testing.B) { t := &mockT{} b.ResetTimer() for i := 0; i < b.N; i++ { @@ -98,7 +98,7 @@ func BenchmarkRequireEqual(b *testing.B) { } } -func BenchmarkTestifyRequireEqual(b *testing.B) { +func BenchmarkTestifyRequireEqualInt64(b *testing.B) { t := &mockT{} b.ResetTimer() for i := 0; i < b.N; i++ { @@ -106,7 +106,7 @@ func BenchmarkTestifyRequireEqual(b *testing.B) { } } -func BenchmarkRequireStringsEqual(b *testing.B) { +func BenchmarkRequireEqualString(b *testing.B) { s1 := strings.Repeat("abacaba", 1024) s2 := strings.Repeat("abacaba", 1024) @@ -117,9 +117,9 @@ func BenchmarkRequireStringsEqual(b *testing.B) { } } -func BenchmarkTestifyRequireStringsEqual(b *testing.B) { - s1 := strings.Repeat("abacaba", 1024*8) - s2 := strings.Repeat("abacaba", 1024*8) +func BenchmarkTestifyRequireEqualString(b *testing.B) { + s1 := strings.Repeat("abacaba", 1024) + s2 := strings.Repeat("abacaba", 1024) mockT := &mockT{} b.ResetTimer() @@ -127,3 +127,25 @@ func BenchmarkTestifyRequireStringsEqual(b *testing.B) { require.Equal(mockT, s1, s2) } } + +func BenchmarkRequireEqualMap(b *testing.B) { + m1 := map[string]string{"a": "b", "c": "d", "e": "f"} + m2 := map[string]string{"a": "b", "c": "d", "e": "f"} + + mockT := &mockT{} + b.ResetTimer() + for i := 0; i < b.N; i++ { + RequireEqual(mockT, m1, m2) + } +} + +func BenchmarkTestifyRequireEqualMap(b *testing.B) { + m1 := map[string]string{"a": "b", "c": "d", "e": "f"} + m2 := map[string]string{"a": "b", "c": "d", "e": "f"} + + mockT := &mockT{} + b.ResetTimer() + for i := 0; i < b.N; i++ { + require.Equal(mockT, m1, m2) + } +} From 2b49e0ea2ffc4fa8bd1387f705c7314b1de93749 Mon Sep 17 00:00:00 2001 From: Arseny Balobanov Date: Sat, 21 Mar 2020 03:52:37 +0300 Subject: [PATCH 6/7] Rename require.go to assertions.go. --- testequal/{require.go => assertions.go} | 0 testequal/{require_test.go => assertions_test.go} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename testequal/{require.go => assertions.go} (100%) rename testequal/{require_test.go => assertions_test.go} (100%) diff --git a/testequal/require.go b/testequal/assertions.go similarity index 100% rename from testequal/require.go rename to testequal/assertions.go diff --git a/testequal/require_test.go b/testequal/assertions_test.go similarity index 100% rename from testequal/require_test.go rename to testequal/assertions_test.go From 4423e722a9ff219bbda3ef8cd81c8aea391a9c87 Mon Sep 17 00:00:00 2001 From: Arseny Balobanov Date: Sun, 22 Mar 2020 06:35:22 +0300 Subject: [PATCH 7/7] Move error message tests from helper test to separate test. --- testequal/assertions_test.go | 17 +++++++++++++++-- testequal/helper_test.go | 9 +-------- testequal/t.go | 3 ++- 3 files changed, 18 insertions(+), 11 deletions(-) diff --git a/testequal/assertions_test.go b/testequal/assertions_test.go index 9eef970..679ee78 100644 --- a/testequal/assertions_test.go +++ b/testequal/assertions_test.go @@ -82,14 +82,27 @@ func TestNotEqual(t *testing.T) { } } -type mockT struct{} +type mockT struct { + errMsg string +} -func (m *mockT) Errorf(format string, args ...interface{}) {} +func (m *mockT) Errorf(format string, args ...interface{}) { + m.errMsg = fmt.Sprintf(format, args...) +} func (m *mockT) FailNow() {} func (m *mockT) Helper() {} +func TestErrorMessage(t *testing.T) { + mockT := &mockT{} + RequireNotEqual(mockT, 1, 1, "1 != 1") + require.Contains(t, mockT.errMsg, "1 != 1") + + RequireEqual(mockT, 1, 2, "%d must be equal to %d", 1, 2) + require.Contains(t, mockT.errMsg, "1 must be equal to 2") +} + func BenchmarkRequireEqualInt64(b *testing.B) { t := &mockT{} b.ResetTimer() diff --git a/testequal/helper_test.go b/testequal/helper_test.go index 5b203a6..bbf31c2 100644 --- a/testequal/helper_test.go +++ b/testequal/helper_test.go @@ -13,7 +13,7 @@ func TestHelper(t *testing.T) { if os.Getenv("FAIL_ASSERTIONS") == "1" { AssertEqual(t, 1, 2, "%d must be equal to %d", 1, 2) AssertNotEqual(t, 1, 1, "1 != 1") - RequireEqual(t, 1, 2, errMsgLog{Exp: 1, Act: 2}) + RequireEqual(t, 1, 2) return } @@ -25,16 +25,9 @@ func TestHelper(t *testing.T) { err := cmd.Run() if e, ok := err.(*exec.ExitError); ok && !e.Success() { require.Contains(t, buf.String(), "helper_test.go:14") - require.Contains(t, buf.String(), "1 must be equal to 2") require.Contains(t, buf.String(), "helper_test.go:15") - require.Contains(t, buf.String(), "1 != 1") require.Contains(t, buf.String(), "helper_test.go:16") return } t.Fatalf("process ran with err %v, want exit status 1", err) } - -type errMsgLog struct { - Exp int - Act int -} diff --git a/testequal/t.go b/testequal/t.go index 2bf598e..ec1e38a 100644 --- a/testequal/t.go +++ b/testequal/t.go @@ -2,7 +2,8 @@ package testequal -// T is an interface wrapper around *testing.T. +// T is an interface wrapper for *testing.T +// that contains only a small subset of methods. type T interface { Errorf(format string, args ...interface{}) Helper()