Merge branch '14-type-switch-task' into 'master'
Resolve "type switch task" Closes #14 See merge request slon/shad-go-private!22
This commit is contained in:
commit
7e14d269e7
5 changed files with 283 additions and 0 deletions
44
testequal/README.md
Normal file
44
testequal/README.md
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
## 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{})
|
||||||
|
```
|
||||||
|
|
||||||
|
Функции проверяют на равенство expected и actual и завершают тест, если проверка не прошла.
|
||||||
|
msgAndArgs попадают в описание ошибки через fmt.Sprintf.
|
||||||
|
|
||||||
|
Пример использования:
|
||||||
|
```
|
||||||
|
func TestMath(t *testing.T) {
|
||||||
|
AssertEqual(t, 1, 2, "1 == 2")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
вывод теста:
|
||||||
|
```
|
||||||
|
=== RUN TestMath
|
||||||
|
--- FAIL: TestMath (0.00s)
|
||||||
|
math_test.go:43: not equal:
|
||||||
|
expected: 1
|
||||||
|
actual : 2
|
||||||
|
message : 1 == 2
|
||||||
|
FAIL
|
||||||
|
FAIL gitlab.com/slon/shad-go/testequal 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
|
31
testequal/assertions.go
Normal file
31
testequal/assertions.go
Normal file
|
@ -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")
|
||||||
|
}
|
164
testequal/assertions_test.go
Normal file
164
testequal/assertions_test.go
Normal file
|
@ -0,0 +1,164 @@
|
||||||
|
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 {
|
||||||
|
errMsg string
|
||||||
|
}
|
||||||
|
|
||||||
|
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()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
RequireEqual(t, int64(1), int64(1))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkTestifyRequireEqualInt64(b *testing.B) {
|
||||||
|
t := &mockT{}
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
require.Equal(t, int64(1), int64(1))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkRequireEqualString(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 BenchmarkTestifyRequireEqualString(b *testing.B) {
|
||||||
|
s1 := strings.Repeat("abacaba", 1024)
|
||||||
|
s2 := strings.Repeat("abacaba", 1024)
|
||||||
|
|
||||||
|
mockT := &mockT{}
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
33
testequal/helper_test.go
Normal file
33
testequal/helper_test.go
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
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)
|
||||||
|
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(), "helper_test.go:15")
|
||||||
|
require.Contains(t, buf.String(), "helper_test.go:16")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
t.Fatalf("process ran with err %v, want exit status 1", err)
|
||||||
|
}
|
11
testequal/t.go
Normal file
11
testequal/t.go
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
// +build !change
|
||||||
|
|
||||||
|
package testequal
|
||||||
|
|
||||||
|
// 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()
|
||||||
|
FailNow()
|
||||||
|
}
|
Loading…
Reference in a new issue