feat(Go-StudyExample):2020/12/31:新增【Go每日一库】go-cmp结构体比较工具

This commit is contained in:
Huangzj
2020-12-31 14:48:58 +08:00
parent 20c2c7fab0
commit 6d079549f1
10 changed files with 465 additions and 0 deletions
+47
View File
@@ -0,0 +1,47 @@
/*
* @Author : huangzj
* @Time : 2020/12/30 14:43
* @Description
*/
package go_cmp
import (
"fmt"
"github.com/google/go-cmp/cmp"
"testing"
)
//测试指向同一个结构体的对象
func TestStruct(t *testing.T) {
u1 := User{Name: "dj", Age: 18}
u2 := User{Name: "dj", Age: 18}
fmt.Println("u1 == u2?", u1 == u2)
fmt.Println("u1 equals u2?", cmp.Equal(u1, u2))
c1 := &Contact{Phone: "123456789", Email: "dj@example.com"}
u1.Contact = c1
u2.Contact = c1
fmt.Println("u1 == u2 with same pointer?", u1 == u2)
fmt.Println("u1 equals u2 with same pointer?", cmp.Equal(u1, u2))
}
//测试指向不同结构体,但是结构体值一致的对象
func TestPoint(t *testing.T) {
u1 := User{Name: "dj", Age: 18}
u2 := User{Name: "dj", Age: 18}
fmt.Println("u1 == u2?", u1 == u2)
fmt.Println("u1 equals u2?", cmp.Equal(u1, u2))
c1 := &Contact{Phone: "123456789", Email: "dj@example.com"}
c2 := &Contact{Phone: "123456789", Email: "dj@example.com"}
u1.Contact = c1
u2.Contact = c2
fmt.Println("u1 == u2 with different pointer?", u1 == u2)
fmt.Println("u1 equals u2 with different pointer?", cmp.Equal(u1, u2))
}
+34
View File
@@ -0,0 +1,34 @@
/*
* @Author : huangzj
* @Time : 2020/12/30 14:03
* @Description:输出两个对象之间的差异
*/
package go_cmp
import (
"fmt"
"testing"
"github.com/google/go-cmp/cmp"
)
type Contact struct {
Phone string
Email string
}
type User struct {
Name string
Age int
Contact *Contact
}
func TestDiff(t *testing.T) {
c1 := &Contact{Phone: "123456789", Email: "dj@example.com"}
c2 := &Contact{Phone: "123456879", Email: "dj2@example.com"}
u1 := User{Name: "dj", Age: 18, Contact: c1}
u2 := User{Name: "dj2", Age: 18, Contact: c2}
fmt.Println(cmp.Diff(u1, u2))
}
+50
View File
@@ -0,0 +1,50 @@
/*
* @Author : huangzj
* @Time : 2020/12/30 14:57
* @Description
*/
package go_cmp
import (
"fmt"
"github.com/google/go-cmp/cmp"
"testing"
)
type NetAddr1 struct {
IP string
Port int
}
func compareNetAddr(a, b NetAddr1) bool {
if a.Port != b.Port {
return false
}
if a.IP != b.IP {
if a.IP == "127.0.0.1" && b.IP == "localhost" {
return true
}
if a.IP == "localhost" && b.IP == "127.0.0.1" {
return true
}
return false
}
return true
}
//自定义比较器:
// 这种方式与上面介绍的自定义Equal()方法有些类似,但更灵活。
// 有时,我们要自定义比较操作的类型定义在第三方包中,这样就无法给它定义Equal方法。
// 这时,我们就可以采用自定义Comparer的方式。
func TestDiyCompareMethod(t *testing.T) {
a1 := NetAddr1{"127.0.0.1", 5000}
a2 := NetAddr1{"localhost", 5000}
fmt.Println("a1 equals a2?", cmp.Equal(a1, a2))
fmt.Println("a1 equals a2 with comparer?", cmp.Equal(a1, a2, cmp.Comparer(compareNetAddr)))
}
+47
View File
@@ -0,0 +1,47 @@
/*
* @Author : huangzj
* @Time : 2020/12/30 14:49
* @Description:自定义Equal方法...
*/
package go_cmp
import (
"fmt"
"github.com/google/go-cmp/cmp"
"testing"
)
type NetAddr struct {
IP string
Port int
}
func (a NetAddr) Equal(b NetAddr) bool {
if a.Port != b.Port {
return false
}
if a.IP != b.IP {
if a.IP == "127.0.0.1" && b.IP == "localhost" {
return true
}
if a.IP == "localhost" && b.IP == "127.0.0.1" {
return true
}
return false
}
return true
}
func TestDiyEqualMethod(t *testing.T) {
a1 := NetAddr{"127.0.0.1", 5000}
a2 := NetAddr{"localhost", 5000}
a3 := NetAddr{"192.168.1.1", 5000}
fmt.Println("a1 equals a2?", cmp.Equal(a1, a2))
fmt.Println("a1 equals a3?", cmp.Equal(a1, a3))
}
+58
View File
@@ -0,0 +1,58 @@
/*
* @Author : huangzj
* @Time : 2020/12/30 15:11
* @Description
*/
package go_cmp
import (
"fmt"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"math"
"testing"
)
type FloatPair struct {
X float64
Y float64
}
func TestBasicFloat(t *testing.T) {
//特殊的浮点数NaNNot a Number),它与任何浮点数都不等,包括它自己
p1 := FloatPair{X: math.NaN()}
p2 := FloatPair{X: math.NaN()}
fmt.Println("p1 equals p2?", cmp.Equal(p1, p2))
//受限于变量的存储空间,就会存在误差
f1 := 0.1
f2 := 0.2
f3 := 0.3
p3 := FloatPair{X: f1 + f2}
p4 := FloatPair{X: f3}
fmt.Println("p3 equals p4?", cmp.Equal(p3, p4))
//Go 语言中这些字面量的运算直接是在编译器完成的,所以同样的值虽然受限于变量的存储空间,但是结果是一样的.
p5 := FloatPair{X: 0.1 + 0.2}
p6 := FloatPair{X: 0.3}
fmt.Println("p5 equals p6?", cmp.Equal(p5, p6))
}
//通过equal方法测试NaN返回结果一致
func TestNanEqual(t *testing.T) {
p1 := FloatPair{X: math.NaN()}
p2 := FloatPair{X: math.NaN()}
fmt.Println("p1 equals p2?", cmp.Equal(p1, p2, cmpopts.EquateNaNs()))
}
//测试误差在一定的范围内 具体表示是:|x-y| ≤ max(fraction*min(|x|, |y|), margin)
func TestCountError(t *testing.T) {
f1 := 0.1
f2 := 0.2
f3 := 0.3
p3 := FloatPair{X: f1 + f2}
p4 := FloatPair{X: f3}
fmt.Println(fmt.Sprintf("实际存储的值分别是:\n %v \n %v", p3.X, p4.X))
fmt.Println("p3 equals p4?", cmp.Equal(p3, p4, cmpopts.EquateApprox(0.1, 0.001)))
}
+95
View File
@@ -0,0 +1,95 @@
/*
* @Author : huangzj
* @Time : 2020/12/30 15:21
* @Description:测试未导出字段
*/
package go_cmp
import (
"fmt"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"reflect"
"testing"
)
//自定义是否比较未导出字段,这个方法和上面的很类似,都是必须制定相应的结构体
func TestDiyExportMethod(t *testing.T) {
u1 := User2{"dj", 18, Address{}}
u2 := User2{"dj", 18, Address{}}
fmt.Println("u1 equals u2?", cmp.Equal(u1, u2, cmp.Exporter(allowUnExportedInType)))
}
//测试只忽略当前对象的未导出属性
func TestExportOne(t *testing.T) {
c1 := Contactx{Phone: "123456789", Email: "dj@example.com"}
c2 := Contactx{Phone: "123456789", Email: "dj@example.com"}
u1 := Userx{"dj", 18, c1}
u2 := Userx{"dj", 18, c2}
//i think 通过 IgnoreUnexported 这个属性可以实现忽略具体类型的未导出字段,但是好像没办法忽略 【指针类型】 的未导出字段
fmt.Println("u1 equals u2?", cmp.Equal(u1, u2, cmpopts.IgnoreUnexported(Userx{})))
}
//结构体中有未导出字段的时候,通过Equal进行比较的时候,会报错,除非设置忽略
func TestExportError(t *testing.T) {
c1 := &Contact1{Phone: "123456789", Email: "dj@example.com"}
c2 := &Contact1{Phone: "123456789", Email: "dj@example.com"}
u1 := User1{"dj", 18, c1}
u2 := User1{"dj", 18, c2}
fmt.Println("u1 equals u2?", cmp.Equal(u1, u2))
}
//使用 【IgnoreUnexported】,但是在结构体下层还有其他非导出的字段也会报错,这个字段没办法深入到里面
func TestExportError2(t *testing.T) {
u11 := User2{"dj", 18, Address{}}
u22 := User2{"dj", 18, Address{}}
fmt.Println("u1 equals u2?", cmp.Equal(u11, u22, cmpopts.IgnoreUnexported(User2{})))
}
func allowUnExportedInType(t reflect.Type) bool {
if t.Name() == "Address" {
return true
}
return false
}
type Contactx struct {
Phone string
Email string
}
type Userx struct {
Name string
Age int
contact Contactx //注意这边是结构体
}
type Contact1 struct {
Phone string
Email string
}
type User1 struct {
Name string
Age int
contact *Contact1
}
type Address struct {
Province string
city string
}
type User2 struct {
Name string
Age int
Address Address
}
+50
View File
@@ -0,0 +1,50 @@
/*
* @Author : huangzj
* @Time : 2020/12/30 14:08
* @Descriptionmap和切片的比较方法
*/
package go_cmp
import (
"fmt"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
"testing"
)
func TestNilEqualEmpty(t *testing.T) {
var s1 []int
var s2 = make([]int, 0)
var m1 map[int]int
var m2 = make(map[int]int)
//Go原生的" == "符号是没办法比较 切片和map的.
//fmt.Println(s1 == s2)
//fmt.Println(m1 == m2)
//这边可以通过Equal来比较 切片和map
fmt.Println("s1 equals s2?", cmp.Equal(s1, s2))
fmt.Println("m1 equals m2?", cmp.Equal(m1, m2))
//空的切片和nil如果要比较相等,需要加上对应的参数
fmt.Println("s1 equals s2 with option?", cmp.Equal(s1, s2, cmpopts.EquateEmpty()))
fmt.Println("m1 equals m2 with option?", cmp.Equal(m1, m2, cmpopts.EquateEmpty()))
}
//比较无序的切片,和本来就没有顺序的map
func TestSliceOutOfOrder(t *testing.T) {
s1 := []int{1, 2, 3, 4}
s2 := []int{4, 3, 2, 1}
fmt.Println("s1 equals s2?", cmp.Equal(s1, s2))
fmt.Println("s1 equals s2 with option?", cmp.Equal(s1, s2, cmpopts.SortSlices(func(i, j int) bool { return i < j })))
fmt.Println()
m1 := map[int]int{1: 10, 2: 20, 3: 30}
m2 := map[int]int{1: 10, 2: 20, 3: 30}
fmt.Println("m1 equals m2?", cmp.Equal(m1, m2))
fmt.Println("m1 equals m2 with option?", cmp.Equal(m1, m2, cmpopts.SortMaps(func(i, j int) bool { return i < j })))
}
+75
View File
@@ -0,0 +1,75 @@
/*
* @Author : huangzj
* @Time : 2020/12/31 14:33
* @Description: 比较的时候转换对应的属性,改变结构体对比的规则
*/
package go_cmp
import (
"fmt"
"github.com/google/go-cmp/cmp"
"testing"
)
type UserO struct {
Name string
Age int
}
func omitAge(u UserO) string {
return u.Name
}
type UserO2 struct {
Name string
Age int
Email string
Address string
}
func omitAge2(u UserO2) UserO2 {
return UserO2{u.Name, 0, u.Email, u.Address}
}
//对某个字段忽略比较,也就是返回字段默认值
func TestOmit(t *testing.T) {
u1 := UserO{Name: "dj", Age: 18}
u2 := UserO{Name: "dj", Age: 28}
//i think 这边omitAge只返回name属性应该是值比较UserO的name属性即可
fmt.Println("u1 equals u2?", cmp.Equal(u1, u2, cmp.Transformer("omitAge", omitAge)))
u3 := UserO2{Name: "dj", Age: 18, Email: "dj@example.com"}
u4 := UserO2{Name: "dj", Age: 28, Email: "dj@example.com"}
u5 := UserO2{Name: "dj", Age: 18, Email: "dj@example.com"}
u6 := UserO2{Name: "dj1", Age: 28, Email: "dj@example.com"}
//i think 这边的omitAge2返回修改之后的UserO2,只把age设置为0,其他属性不变,说明还是会参与到比较
fmt.Println("u3 equals u4?", cmp.Equal(u3, u4, cmp.Transformer("omitAge", omitAge2)))
fmt.Println("u5 equals u5?", cmp.Equal(u5, u6, cmp.Transformer("omitAge", omitAge2)))
}
type NetAddrO struct {
IP string
Port int
}
func transformLocalhost(a NetAddrO) NetAddrO {
if a.IP == "localhost" {
return NetAddrO{IP: "127.0.0.1", Port: a.Port}
}
return a
}
//对结构体进行对比的时候,修改某字段的值
func TestTransformValue(t *testing.T) {
a1 := NetAddrO{"127.0.0.1", 5000}
a2 := NetAddrO{"localhost", 5000}
//这边是把NetAddr对应的IP进行转换,localhost == 127.0.0.1 所以直接进行转换,比较的结果就会一致
fmt.Println("a1 equals a2?", cmp.Equal(a1, a2, cmp.Transformer("localhost", transformLocalhost)))
}
+7
View File
@@ -0,0 +1,7 @@
获取包命令:go get -u github.com/google/go-cmp
参考地址:https://segmentfault.com/a/1190000021997004
参考地址:https://github.com/google/go-cmp
参考地址:https://github.com/darjun/go-daily-lib
+2
View File
@@ -43,6 +43,8 @@ json与struct之间转换处理工具使用示例
2020/12/29:新增【Go每日一库】emial发送邮件
2020/12/31:新增【Go每日一库】go-cmp结构体比较工具
# mod vendor模式加载包
通过go mod的方式加载的github上面的包会有报红的问题,但是包本身是可以运行的,这样就是会有一个问题,如果你想要点击去看方法的内容,没办法做到