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() +}