云原生|Kubernetes Operator测试实例

目录

一、主要代码介绍

(一)变量定义:

(二)测试程序入口

(三)before函数

(四)after函数

二、实际测试

(一)块划分

(二)块设置

(三)条件判断

(四)循环判断


一、主要代码介绍

首先在kubebuilder的框架生成时,kubebuilder框架本身会将测试代码的文件生成

进入该文件之后可以看到测试文件使用的测试包为

ginkgo和gomega包为TDD类型的go语言的实现,TDD即test-driven-development测试驱动开发,关于TDD的详情可以自己google,本文不做展开描述

接下来我们看代码部分,代码部分可以分为4部分

1.变量定义

2.测试入口

3.before函数

4.after函数

(一)变量定义:

包含了一个rest.Config的指针,这个指针实际表示的即为测试集群的配置文件即kubeconfig,client.Client为一个测试集群的客户端,envtest.Environment即为测试环境的抽象对象

(二)测试程序入口

这里注册了测试程序中报错的handler处理函数,以及兼容原生go test 的运行函数

(三)before函数

在before函数中设置了日志相关的设置,以及读取CRD文件的,以及启动测试环境,将crd的schema添加到k8s的默认的schema中以及根据这个schema生成对应的client

然而,这个自动生成的文件缺少了实际启动控制器的方法。 上面的代码将会建立一个和您自定义的 Kind 交互的客户端,但是无法测试您的控制器的行为。 如果你想要测试自定义的控制器逻辑,您需要添加一些相似的管理逻辑到 BeforeSuite() 函数, 这样就可以将你的自定义控制器运行在这个测试集群上。

一旦添加了下面的代码,你将可以删除掉上面的 k8sClient,因为你可以从 manager 中获取到 k8sClient (如下所示)。

k8sManager, err := ctrl.NewManager(cfg, ctrl.Options{
    Scheme: scheme.Scheme,
})
Expect(err).ToNot(HaveOccurred())
 
err = (&CronJobReconciler{
    Client: k8sManager.GetClient(),
    Log:    ctrl.Log.WithName("controllers").WithName("CronJob"),
}).SetupWithManager(k8sManager)
Expect(err).ToNot(HaveOccurred())
 
go func() {
    err = k8sManager.Start(ctrl.SetupSignalHandler())
    Expect(err).ToNot(HaveOccurred())
}()
k8sClient = k8sManager.GetClient()
Expect(k8sClient).ToNot(BeNil())
 
close(done)

熟悉kububuilder框架的程序员应该能发现这里的代码即为生成的默认main函数中和初始化manager部分的代码是相同的

其中可以看到大量的Expect()函数,可以看到下图

该函数主要是接受一个任意类型的变量并将起转换为Assertion类型的接口,其中Assertion类型的接口则包含了一下几种方法

这些方法即表示传入的变量是否应该为某个值,即以下的代码

Expect(cfg).NotTo(BeNil())

等价于

if cfg != nil{
    return err
}

(四)after函数

该函数主要停止了testEnv

当然由于这里还是需要使用client到k8s中去创建对应的crd,注意如果你的集群中已经安装了对应的crd的operator的话实际在集群中创建的cr资源仍然会被集群中的operator list-watch到,可能会导致集群的operator和测试代码中的operator对资源处理进行抢占,如果不希望发生这样的情况,可以选择在生成client时直接选择使用集群的配置文件去生成对应的client并且不再额外启动manger,或者选择使用一个并未安装operator的集群进行测试。

现在我们已经了解了suite_test文件的主要代码,接下来我们开始根据我们的实际情况来编写测试代码


二、实际测试

(一)块划分

在较为复杂的项目中,功能点会有非常的多,相应的我们的测试用例也会非常的多,这时测试用例的编排就显得尤为必要,良好的排布用例可以方便我们在后期的维护中快速的找到对应功能的测试用例,进行更改或者开发

在gomega中我们则使用使用了以下几类函数来对用例进行划块Describe、Context、When、It、Specify,这里使用的是几类函数而不是说几个函数是因为这几个函数和fmt包中的print函数相同除了基础Print函数外还有Sprint和Fprint函数,而对应到Describe、Context、When、It、Specify这几类中则是P,F,X即Describe,PDescribe,FDescribe,XDescribe等等,而他们的含义则是在基础函数的原本功能上增加了Focus、Pend具体的说明将在下文中说到。

首先是Describe、Context、When这三类函数,他们可以说基本上是等价的,他们的功能就是用来划分你的测试用例,将功能模块相同的用例放在一起,在他们的闭包函数中可以包含块统一处理函数如BeforeEach、AfterEach等这些函数将在第二部分块设置中进行详细的说明

那么为什么又说这三者基本是等价的呢,我们可以通过源码看到Context类的函数是直接等于Describe函数的,而When函数的实际实现和Describe的实现基本相同只不过会在text的前添加when字符串,可能这样仍然不好理解,我们看以下源码:

Describe:

Context:

When:

可以清晰的看到Context类的函数是直接等于Describe类函数的,所以这两类函数是完全等价的,而对照来看Describe类函数和When类函数其中唯一的差别则是在When函数会自动在text值前加上When字符串,然而在实际的使用中则没有差别,唯一的差别就是测试报告的显示上

那么现在明确了Describe、Context、When这三类函数的功能则是为了划分块将功能模块相同的测试用例划分到统一个块中,但是如果仅仅是为了将功能模块相同的用例放在一起那么实际对于开发者来说仍然可以通过良好的编码习惯来进行控制,那么这些函数产生的实际意义其实除了块划分以外,另一个比较重要的点则是对同一个块中的用例进行统一处理这部分的说明则将在第二部分块设置中说明。

那么接下来就是另外两类函数It、Specify,相应的其中It和Specify也是完全等价的,话不多说直接上源码

我们可以看到Specify类的函数是完全等价于It类函数的,并且我们发现It函数和Describe函数实现也是非常的像,唯一不同的地方就是NodeType,//todo

在实际的使用中我们往往会使用块函数组织一个较多较为复杂的测试用例,但是在某次的代码改动中如果我们只更改了某个模块或者功能的代码,实际上我们并不用执行全部的测试用例,而是只执行这一部分功能的测试用例即可但是如果是注释其他测试代码或者删除其他测试代码,这非常不优雅,而P,F,X的函数则是提供了一种快速标记的方法,比如我们只需要执行某个Describe块中的用例甚至某个It块中的用例,那么我们则可以将该块的Describe或者It函数改为FDescribe或者FIt,则在执行是gomage则会只执行对应块中的用例,或者我们只有某个功能模块没有更改时,那么这个模块的用例则不用再次执行,我们只需要将Describe或者It该为PDescribe或者PIt那么gomega在执行时则会忽略这个块中的用例,但是由于我们的块中存在嵌套,如果内层和外层同时设置focus时则外层的focus会失效只执行内存的。

但是在功能较为相似的的块划分中如果我们可以更抽象的表示一些用例则可以更加简化我们的测试代码,这时我们则可以选择使用DescribeTable类函数,同样的它也拥有P、F、X的函数,不同的是我们可以将功能类似的函数简化如下边的一个官方的例子

DescribeTable("a simple table",
    func(x int, y int, expected bool) {
        Ω(x > y).Should(Equal(expected))
    },
    Entry("x > y", 1, 0, true),
    Entry("x == y", 0, 0, false),
    Entry("x < y", 0, 1, false),
)

当然对于一个较为复杂或者运行时常较为长的用例中我们希望可以观察到用例执行到具体的位置,在传统的写法中我们则会进行日志的打印,而在gomega中我们则使用By函数,它除了进行简单的标识打印,同时它还可以接受更多的函数,并在执行到By函数时立即执行传入的函数


(二)块设置

在第一部分中我们通过块函数将较为复杂的测试用例根据模块功能等进行了具体的划分,但是在相同的功能的模块的测试用例中我们往会想在用例代码执行前或后做些前置操作或后置操作,如生成测试数据,删除测试数据等,而这些代码往往是重复的独立于测试逻辑之外的,如果写在每个It块的函数中则需要写大量的重复的代码,这时我们则希望可以有一个函数可以统一的在It 的前生成测试数据或在It执行后删除测试数据,这时我们则可以选择使用块设置函数如全局的测试用例的设置BeforeSuite、AfterSuite,或者在具体的块中进行BeforeEach、AfterEach、JustBeforeEach等

BeforeSuite:在全部的测试代码执行之前执行的代码

AfterSuite:在全部的测试代码执行之前执行的代码

BeforeEach:在块测试代码执行之前执行的代码

AfterEach:在块测试代码执行之前执行的代码

JustBeforeEach:和BeforeEach相似,保证在BeforeEach之后运行。再it之前运行

JustAfterEach:和AfterEach相似,保证再AfterEach之前运行,再it之后运行

(三)条件判断

Ω:包装了一个实际的值可以返回一个Assertion,允许进行对返回值进行断言

Expect:包装了一个实际的值可以返回一个Assertion,允许进行对返回值进行断言等价于Ω

ExpectWithOffset:和Expect使用方式相同,等价于Expect(...).WithOffset()

demo:

// 执行创建book cr资源,判断结果是否成功
Expect(k8sClient.Create(context.TODO(), book)).To(Succeed())

在gomega包中内置了很多的类似这样的匹配类型如下

package gomega
 
import (
   "time"
 
   "github.com/google/go-cmp/cmp"
   "github.com/onsi/gomega/matchers"
   "github.com/onsi/gomega/types"
)
 
// Equal uses reflect.DeepEqual to compare actual with expected.  Equal is strict about
// types when performing comparisons.
// It is an error for both actual and expected to be nil.  Use BeNil() instead.
func Equal(expected interface{}) types.GomegaMatcher {
   return &matchers.EqualMatcher{
      Expected: expected,
   }
}
 
// BeEquivalentTo is more lax than Equal, allowing equality between different types.
// This is done by converting actual to have the type of expected before
// attempting equality with reflect.DeepEqual.
// It is an error for actual and expected to be nil.  Use BeNil() instead.
func BeEquivalentTo(expected interface{}) types.GomegaMatcher {
   return &matchers.BeEquivalentToMatcher{
      Expected: expected,
   }
}
 
// BeComparableTo uses gocmp.Equal from github.com/google/go-cmp (instead of reflect.DeepEqual) to perform a deep comparison.
// You can pass cmp.Option as options.
// It is an error for actual and expected to be nil.  Use BeNil() instead.
func BeComparableTo(expected interface{}, opts ...cmp.Option) types.GomegaMatcher {
   return &matchers.BeComparableToMatcher{
      Expected: expected,
      Options:  opts,
   }
}
 
// BeIdenticalTo uses the == operator to compare actual with expected.
// BeIdenticalTo is strict about types when performing comparisons.
// It is an error for both actual and expected to be nil.  Use BeNil() instead.
func BeIdenticalTo(expected interface{}) types.GomegaMatcher {
   return &matchers.BeIdenticalToMatcher{
      Expected: expected,
   }
}
 
// BeNil succeeds if actual is nil
func BeNil() types.GomegaMatcher {
   return &matchers.BeNilMatcher{}
}
 
// BeTrue succeeds if actual is true
func BeTrue() types.GomegaMatcher {
   return &matchers.BeTrueMatcher{}
}
 
// BeFalse succeeds if actual is false
func BeFalse() types.GomegaMatcher {
   return &matchers.BeFalseMatcher{}
}
 
// HaveOccurred succeeds if actual is a non-nil error
// The typical Go error checking pattern looks like:
//
// err := SomethingThatMightFail()
// Expect(err).ShouldNot(HaveOccurred())
func HaveOccurred() types.GomegaMatcher {
   return &matchers.HaveOccurredMatcher{}
}
 
// Succeed passes if actual is a nil error
// Succeed is intended to be used with functions that return a single error value. Instead of
//
// err := SomethingThatMightFail()
// Expect(err).ShouldNot(HaveOccurred())
//
// You can write:
//
// Expect(SomethingThatMightFail()).Should(Succeed())
//
// It is a mistake to use Succeed with a function that has multiple return values.  Gomega's Ω and Expect
// functions automatically trigger failure if any return values after the first return value are non-zero/non-nil.
// This means that Ω(MultiReturnFunc()).ShouldNot(Succeed()) can never pass.
func Succeed() types.GomegaMatcher {
   return &matchers.SucceedMatcher{}
}
 
// MatchError succeeds if actual is a non-nil error that matches the passed in string/error.
//
// These are valid use-cases:
//
// Expect(err).Should(MatchError("an error")) //asserts that err.Error() == "an error"
// Expect(err).Should(MatchError(SomeError)) //asserts that err == SomeError (via reflect.DeepEqual)
//
// It is an error for err to be nil or an object that does not implement the Error interface
func MatchError(expected interface{}) types.GomegaMatcher {
   return &matchers.MatchErrorMatcher{
      Expected: expected,
   }
}
 
// BeClosed succeeds if actual is a closed channel.
// It is an error to pass a non-channel to BeClosed, it is also an error to pass nil
//
// In order to check whether or not the channel is closed, Gomega must try to read from the channel
// (even in the `ShouldNot(BeClosed())` case).  You should keep this in mind if you wish to make subsequent assertions about
// values coming down the channel.
//
// Also, if you are testing that a *buffered* channel is closed you must first read all values out of the channel before
// asserting that it is closed (it is not possible to detect that a buffered-channel has been closed until all its buffered values are read).
//
// Finally, as a corollary: it is an error to check whether or not a send-only channel is closed.
func BeClosed() types.GomegaMatcher {
   return &matchers.BeClosedMatcher{}
}
 
// Receive succeeds if there is a value to be received on actual.
// Actual must be a channel (and cannot be a send-only channel) -- anything else is an error.
//
// Receive returns immediately and never blocks:
//
// - If there is nothing on the channel `c` then Expect(c).Should(Receive()) will fail and Ω(c).ShouldNot(Receive()) will pass.
//
// - If the channel `c` is closed then Expect(c).Should(Receive()) will fail and Ω(c).ShouldNot(Receive()) will pass.
//
// - If there is something on the channel `c` ready to be read, then Expect(c).Should(Receive()) will pass and Ω(c).ShouldNot(Receive()) will fail.
//
// If you have a go-routine running in the background that will write to channel `c` you can:
//
// Eventually(c).Should(Receive())
//
// This will timeout if nothing gets sent to `c` (you can modify the timeout interval as you normally do with `Eventually`)
//
// A similar use-case is to assert that no go-routine writes to a channel (for a period of time).  You can do this with `Consistently`:
//
// Consistently(c).ShouldNot(Receive())
//
// You can pass `Receive` a matcher.  If you do so, it will match the received object against the matcher.  For example:
//
// Expect(c).Should(Receive(Equal("foo")))
//
// When given a matcher, `Receive` will always fail if there is nothing to be received on the channel.
//
// Passing Receive a matcher is especially useful when paired with Eventually:
//
// Eventually(c).Should(Receive(ContainSubstring("bar")))
//
// will repeatedly attempt to pull values out of `c` until a value matching "bar" is received.
//
// Finally, if you want to have a reference to the value *sent* to the channel you can pass the `Receive` matcher a pointer to a variable of the appropriate type:
//
// var myThing thing
// Eventually(thingChan).Should(Receive(&myThing))
// Expect(myThing.Sprocket).Should(Equal("foo"))
// Expect(myThing.IsValid()).Should(BeTrue())
func Receive(args ...interface{}) types.GomegaMatcher {
   var arg interface{}
   if len(args) > 0 {
      arg = args[0]
   }
 
   return &matchers.ReceiveMatcher{
      Arg: arg,
   }
}
 
// BeSent succeeds if a value can be sent to actual.
// Actual must be a channel (and cannot be a receive-only channel) that can sent the type of the value passed into BeSent -- anything else is an error.
// In addition, actual must not be closed.
//
// BeSent never blocks:
//
// - If the channel `c` is not ready to receive then Expect(c).Should(BeSent("foo")) will fail immediately
// - If the channel `c` is eventually ready to receive then Eventually(c).Should(BeSent("foo")) will succeed.. presuming the channel becomes ready to receive  before Eventually's timeout
// - If the channel `c` is closed then Expect(c).Should(BeSent("foo")) and Ω(c).ShouldNot(BeSent("foo")) will both fail immediately
//
// Of course, the value is actually sent to the channel.  The point of `BeSent` is less to make an assertion about the availability of the channel (which is typically an implementation detail that your test should not be concerned with).
// Rather, the point of `BeSent` is to make it possible to easily and expressively write tests that can timeout on blocked channel sends.
func BeSent(arg interface{}) types.GomegaMatcher {
   return &matchers.BeSentMatcher{
      Arg: arg,
   }
}
 
// MatchRegexp succeeds if actual is a string or stringer that matches the
// passed-in regexp.  Optional arguments can be provided to construct a regexp
// via fmt.Sprintf().
func MatchRegexp(regexp string, args ...interface{}) types.GomegaMatcher {
   return &matchers.MatchRegexpMatcher{
      Regexp: regexp,
      Args:   args,
   }
}
 
// ContainSubstring succeeds if actual is a string or stringer that contains the
// passed-in substring.  Optional arguments can be provided to construct the substring
// via fmt.Sprintf().
func ContainSubstring(substr string, args ...interface{}) types.GomegaMatcher {
   return &matchers.ContainSubstringMatcher{
      Substr: substr,
      Args:   args,
   }
}
 
// HavePrefix succeeds if actual is a string or stringer that contains the
// passed-in string as a prefix.  Optional arguments can be provided to construct
// via fmt.Sprintf().
func HavePrefix(prefix string, args ...interface{}) types.GomegaMatcher {
   return &matchers.HavePrefixMatcher{
      Prefix: prefix,
      Args:   args,
   }
}
 
// HaveSuffix succeeds if actual is a string or stringer that contains the
// passed-in string as a suffix.  Optional arguments can be provided to construct
// via fmt.Sprintf().
func HaveSuffix(suffix string, args ...interface{}) types.GomegaMatcher {
   return &matchers.HaveSuffixMatcher{
      Suffix: suffix,
      Args:   args,
   }
}
 
// MatchJSON succeeds if actual is a string or stringer of JSON that matches
// the expected JSON.  The JSONs are decoded and the resulting objects are compared via
// reflect.DeepEqual so things like key-ordering and whitespace shouldn't matter.
func MatchJSON(json interface{}) types.GomegaMatcher {
   return &matchers.MatchJSONMatcher{
      JSONToMatch: json,
   }
}
 
// MatchXML succeeds if actual is a string or stringer of XML that matches
// the expected XML.  The XMLs are decoded and the resulting objects are compared via
// reflect.DeepEqual so things like whitespaces shouldn't matter.
func MatchXML(xml interface{}) types.GomegaMatcher {
   return &matchers.MatchXMLMatcher{
      XMLToMatch: xml,
   }
}
 
// MatchYAML succeeds if actual is a string or stringer of YAML that matches
// the expected YAML.  The YAML's are decoded and the resulting objects are compared via
// reflect.DeepEqual so things like key-ordering and whitespace shouldn't matter.
func MatchYAML(yaml interface{}) types.GomegaMatcher {
   return &matchers.MatchYAMLMatcher{
      YAMLToMatch: yaml,
   }
}
 
// BeEmpty succeeds if actual is empty.  Actual must be of type string, array, map, chan, or slice.
func BeEmpty() types.GomegaMatcher {
   return &matchers.BeEmptyMatcher{}
}
 
// HaveLen succeeds if actual has the passed-in length.  Actual must be of type string, array, map, chan, or slice.
func HaveLen(count int) types.GomegaMatcher {
   return &matchers.HaveLenMatcher{
      Count: count,
   }
}
 
// HaveCap succeeds if actual has the passed-in capacity.  Actual must be of type array, chan, or slice.
func HaveCap(count int) types.GomegaMatcher {
   return &matchers.HaveCapMatcher{
      Count: count,
   }
}
 
// BeZero succeeds if actual is the zero value for its type or if actual is nil.
func BeZero() types.GomegaMatcher {
   return &matchers.BeZeroMatcher{}
}
 
// ContainElement succeeds if actual contains the passed in element. By default
// ContainElement() uses Equal() to perform the match, however a matcher can be
// passed in instead:
//
// Expect([]string{"Foo", "FooBar"}).Should(ContainElement(ContainSubstring("Bar")))
//
// Actual must be an array, slice or map. For maps, ContainElement searches
// through the map's values.
//
// If you want to have a copy of the matching element(s) found you can pass a
// pointer to a variable of the appropriate type. If the variable isn't a slice
// or map, then exactly one match will be expected and returned. If the variable
// is a slice or map, then at least one match is expected and all matches will be
// stored in the variable.
//
// var findings []string
// Expect([]string{"Foo", "FooBar"}).Should(ContainElement(ContainSubString("Bar", &findings)))
func ContainElement(element interface{}, result ...interface{}) types.GomegaMatcher {
   return &matchers.ContainElementMatcher{
      Element: element,
      Result:  result,
   }
}
 
// BeElementOf succeeds if actual is contained in the passed in elements.
// BeElementOf() always uses Equal() to perform the match.
// When the passed in elements are comprised of a single element that is either an Array or Slice, BeElementOf() behaves
// as the reverse of ContainElement() that operates with Equal() to perform the match.
//
// Expect(2).Should(BeElementOf([]int{1, 2}))
// Expect(2).Should(BeElementOf([2]int{1, 2}))
//
// Otherwise, BeElementOf() provides a syntactic sugar for Or(Equal(_), Equal(_), ...):
//
// Expect(2).Should(BeElementOf(1, 2))
//
// Actual must be typed.
func BeElementOf(elements ...interface{}) types.GomegaMatcher {
   return &matchers.BeElementOfMatcher{
      Elements: elements,
   }
}
 
// BeKeyOf succeeds if actual is contained in the keys of the passed in map.
// BeKeyOf() always uses Equal() to perform the match between actual and the map keys.
//
// Expect("foo").Should(BeKeyOf(map[string]bool{"foo": true, "bar": false}))
func BeKeyOf(element interface{}) types.GomegaMatcher {
   return &matchers.BeKeyOfMatcher{
      Map: element,
   }
}
 
// ConsistOf succeeds if actual contains precisely the elements passed into the matcher.  The ordering of the elements does not matter.
// By default ConsistOf() uses Equal() to match the elements, however custom matchers can be passed in instead.  Here are some examples:
//
// Expect([]string{"Foo", "FooBar"}).Should(ConsistOf("FooBar", "Foo"))
// Expect([]string{"Foo", "FooBar"}).Should(ConsistOf(ContainSubstring("Bar"), "Foo"))
// Expect([]string{"Foo", "FooBar"}).Should(ConsistOf(ContainSubstring("Foo"), ContainSubstring("Foo")))
//
// Actual must be an array, slice or map.  For maps, ConsistOf matches against the map's values.
//
// You typically pass variadic arguments to ConsistOf (as in the examples above).  However, if you need to pass in a slice you can provided that it
// is the only element passed in to ConsistOf:
//
// Expect([]string{"Foo", "FooBar"}).Should(ConsistOf([]string{"FooBar", "Foo"}))
//
// Note that Go's type system does not allow you to write this as ConsistOf([]string{"FooBar", "Foo"}...) as []string and []interface{} are different types - hence the need for this special rule.
func ConsistOf(elements ...interface{}) types.GomegaMatcher {
   return &matchers.ConsistOfMatcher{
      Elements: elements,
   }
}
 
// ContainElements succeeds if actual contains the passed in elements. The ordering of the elements does not matter.
// By default ContainElements() uses Equal() to match the elements, however custom matchers can be passed in instead. Here are some examples:
//
// Expect([]string{"Foo", "FooBar"}).Should(ContainElements("FooBar"))
// Expect([]string{"Foo", "FooBar"}).Should(ContainElements(ContainSubstring("Bar"), "Foo"))
//
// Actual must be an array, slice or map.
// For maps, ContainElements searches through the map's values.
func ContainElements(elements ...interface{}) types.GomegaMatcher {
   return &matchers.ContainElementsMatcher{
      Elements: elements,
   }
}
 
// HaveEach succeeds if actual solely contains elements that match the passed in element.
// Please note that if actual is empty, HaveEach always will succeed.
// By default HaveEach() uses Equal() to perform the match, however a
// matcher can be passed in instead:
//
// Expect([]string{"Foo", "FooBar"}).Should(HaveEach(ContainSubstring("Foo")))
//
// Actual must be an array, slice or map.
// For maps, HaveEach searches through the map's values.
func HaveEach(element interface{}) types.GomegaMatcher {
   return &matchers.HaveEachMatcher{
      Element: element,
   }
}
 
// HaveKey succeeds if actual is a map with the passed in key.
// By default HaveKey uses Equal() to perform the match, however a
// matcher can be passed in instead:
//
// Expect(map[string]string{"Foo": "Bar", "BazFoo": "Duck"}).Should(HaveKey(MatchRegexp(`.+Foo$`)))
func HaveKey(key interface{}) types.GomegaMatcher {
   return &matchers.HaveKeyMatcher{
      Key: key,
   }
}
 
// HaveKeyWithValue succeeds if actual is a map with the passed in key and value.
// By default HaveKeyWithValue uses Equal() to perform the match, however a
// matcher can be passed in instead:
//
// Expect(map[string]string{"Foo": "Bar", "BazFoo": "Duck"}).Should(HaveKeyWithValue("Foo", "Bar"))
// Expect(map[string]string{"Foo": "Bar", "BazFoo": "Duck"}).Should(HaveKeyWithValue(MatchRegexp(`.+Foo$`), "Bar"))
func HaveKeyWithValue(key interface{}, value interface{}) types.GomegaMatcher {
   return &matchers.HaveKeyWithValueMatcher{
      Key:   key,
      Value: value,
   }
}
 
// HaveField succeeds if actual is a struct and the value at the passed in field
// matches the passed in matcher.  By default HaveField used Equal() to perform the match,
// however a matcher can be passed in in stead.
//
// The field must be a string that resolves to the name of a field in the struct.  Structs can be traversed
// using the '.' delimiter.  If the field ends with '()' a method named field is assumed to exist on the struct and is invoked.
// Such methods must take no arguments and return a single value:
//
// type Book struct {
//     Title string
//     Author Person
// }
// type Person struct {
//     FirstName string
//     LastName string
//     DOB time.Time
// }
// Expect(book).To(HaveField("Title", "Les Miserables"))
// Expect(book).To(HaveField("Title", ContainSubstring("Les"))
// Expect(book).To(HaveField("Author.FirstName", Equal("Victor"))
// Expect(book).To(HaveField("Author.DOB.Year()", BeNumerically("<", 1900))
func HaveField(field string, expected interface{}) types.GomegaMatcher {
   return &matchers.HaveFieldMatcher{
      Field:    field,
      Expected: expected,
   }
}
 
// HaveExistingField succeeds if actual is a struct and the specified field
// exists.
//
// HaveExistingField can be combined with HaveField in order to cover use cases
// with optional fields. HaveField alone would trigger an error in such situations.
//
// Expect(MrHarmless).NotTo(And(HaveExistingField("Title"), HaveField("Title", "Supervillain")))
func HaveExistingField(field string) types.GomegaMatcher {
   return &matchers.HaveExistingFieldMatcher{
      Field: field,
   }
}
 
// HaveValue applies the given matcher to the value of actual, optionally and
// repeatedly dereferencing pointers or taking the concrete value of interfaces.
// Thus, the matcher will always be applied to non-pointer and non-interface
// values only. HaveValue will fail with an error if a pointer or interface is
// nil. It will also fail for more than 31 pointer or interface dereferences to
// guard against mistakenly applying it to arbitrarily deep linked pointers.
//
// HaveValue differs from gstruct.PointTo in that it does not expect actual to
// be a pointer (as gstruct.PointTo does) but instead also accepts non-pointer
// and even interface values.
//
// actual := 42
// Expect(actual).To(HaveValue(42))
// Expect(&actual).To(HaveValue(42))
func HaveValue(matcher types.GomegaMatcher) types.GomegaMatcher {
   return &matchers.HaveValueMatcher{
      Matcher: matcher,
   }
}
 
// BeNumerically performs numerical assertions in a type-agnostic way.
// Actual and expected should be numbers, though the specific type of
// number is irrelevant (float32, float64, uint8, etc...).
//
// There are six, self-explanatory, supported comparators:
//
// Expect(1.0).Should(BeNumerically("==", 1))
// Expect(1.0).Should(BeNumerically("~", 0.999, 0.01))
// Expect(1.0).Should(BeNumerically(">", 0.9))
// Expect(1.0).Should(BeNumerically(">=", 1.0))
// Expect(1.0).Should(BeNumerically("<", 3))
// Expect(1.0).Should(BeNumerically("<=", 1.0))
func BeNumerically(comparator string, compareTo ...interface{}) types.GomegaMatcher {
   return &matchers.BeNumericallyMatcher{
      Comparator: comparator,
      CompareTo:  compareTo,
   }
}
 
// BeTemporally compares time.Time's like BeNumerically
// Actual and expected must be time.Time. The comparators are the same as for BeNumerically
//
// Expect(time.Now()).Should(BeTemporally(">", time.Time{}))
// Expect(time.Now()).Should(BeTemporally("~", time.Now(), time.Second))
func BeTemporally(comparator string, compareTo time.Time, threshold ...time.Duration) types.GomegaMatcher {
   return &matchers.BeTemporallyMatcher{
      Comparator: comparator,
      CompareTo:  compareTo,
      Threshold:  threshold,
   }
}
 
// BeAssignableToTypeOf succeeds if actual is assignable to the type of expected.
// It will return an error when one of the values is nil.
//
// Expect(0).Should(BeAssignableToTypeOf(0))         // Same values
// Expect(5).Should(BeAssignableToTypeOf(-1))        // different values same type
// Expect("foo").Should(BeAssignableToTypeOf("bar")) // different values same type
// Expect(struct{ Foo string }{}).Should(BeAssignableToTypeOf(struct{ Foo string }{}))
func BeAssignableToTypeOf(expected interface{}) types.GomegaMatcher {
   return &matchers.AssignableToTypeOfMatcher{
      Expected: expected,
   }
}
 
// Panic succeeds if actual is a function that, when invoked, panics.
// Actual must be a function that takes no arguments and returns no results.
func Panic() types.GomegaMatcher {
   return &matchers.PanicMatcher{}
}
 
// PanicWith succeeds if actual is a function that, when invoked, panics with a specific value.
// Actual must be a function that takes no arguments and returns no results.
//
// By default PanicWith uses Equal() to perform the match, however a
// matcher can be passed in instead:
//
// Expect(fn).Should(PanicWith(MatchRegexp(`.+Foo$`)))
func PanicWith(expected interface{}) types.GomegaMatcher {
   return &matchers.PanicMatcher{Expected: expected}
}
 
// BeAnExistingFile succeeds if a file exists.
// Actual must be a string representing the abs path to the file being checked.
func BeAnExistingFile() types.GomegaMatcher {
   return &matchers.BeAnExistingFileMatcher{}
}
 
// BeARegularFile succeeds if a file exists and is a regular file.
// Actual must be a string representing the abs path to the file being checked.
func BeARegularFile() types.GomegaMatcher {
   return &matchers.BeARegularFileMatcher{}
}
 
// BeADirectory succeeds if a file exists and is a directory.
// Actual must be a string representing the abs path to the file being checked.
func BeADirectory() types.GomegaMatcher {
   return &matchers.BeADirectoryMatcher{}
}
 
// HaveHTTPStatus succeeds if the Status or StatusCode field of an HTTP response matches.
// Actual must be either a *http.Response or *httptest.ResponseRecorder.
// Expected must be either an int or a string.
//
// Expect(resp).Should(HaveHTTPStatus(http.StatusOK))   // asserts that resp.StatusCode == 200
// Expect(resp).Should(HaveHTTPStatus("404 Not Found")) // asserts that resp.Status == "404 Not Found"
// Expect(resp).Should(HaveHTTPStatus(http.StatusOK, http.StatusNoContent))   // asserts that resp.StatusCode == 200 || resp.StatusCode == 204
func HaveHTTPStatus(expected ...interface{}) types.GomegaMatcher {
   return &matchers.HaveHTTPStatusMatcher{Expected: expected}
}
 
// HaveHTTPHeaderWithValue succeeds if the header is found and the value matches.
// Actual must be either a *http.Response or *httptest.ResponseRecorder.
// Expected must be a string header name, followed by a header value which
// can be a string, or another matcher.
func HaveHTTPHeaderWithValue(header string, value interface{}) types.GomegaMatcher {
   return &matchers.HaveHTTPHeaderWithValueMatcher{
      Header: header,
      Value:  value,
   }
}
 
// HaveHTTPBody matches if the body matches.
// Actual must be either a *http.Response or *httptest.ResponseRecorder.
// Expected must be either a string, []byte, or other matcher
func HaveHTTPBody(expected interface{}) types.GomegaMatcher {
   return &matchers.HaveHTTPBodyMatcher{Expected: expected}
}
 
// And succeeds only if all of the given matchers succeed.
// The matchers are tried in order, and will fail-fast if one doesn't succeed.
//
// Expect("hi").To(And(HaveLen(2), Equal("hi"))
//
// And(), Or(), Not() and WithTransform() allow matchers to be composed into complex expressions.
func And(ms ...types.GomegaMatcher) types.GomegaMatcher {
   return &matchers.AndMatcher{Matchers: ms}
}
 
// SatisfyAll is an alias for And().
//
// Expect("hi").Should(SatisfyAll(HaveLen(2), Equal("hi")))
func SatisfyAll(matchers ...types.GomegaMatcher) types.GomegaMatcher {
   return And(matchers...)
}
 
// Or succeeds if any of the given matchers succeed.
// The matchers are tried in order and will return immediately upon the first successful match.
//
// Expect("hi").To(Or(HaveLen(3), HaveLen(2))
//
// And(), Or(), Not() and WithTransform() allow matchers to be composed into complex expressions.
func Or(ms ...types.GomegaMatcher) types.GomegaMatcher {
   return &matchers.OrMatcher{Matchers: ms}
}
 
// SatisfyAny is an alias for Or().
//
// Expect("hi").SatisfyAny(Or(HaveLen(3), HaveLen(2))
func SatisfyAny(matchers ...types.GomegaMatcher) types.GomegaMatcher {
   return Or(matchers...)
}
 
// Not negates the given matcher; it succeeds if the given matcher fails.
//
// Expect(1).To(Not(Equal(2))
//
// And(), Or(), Not() and WithTransform() allow matchers to be composed into complex expressions.
func Not(matcher types.GomegaMatcher) types.GomegaMatcher {
   return &matchers.NotMatcher{Matcher: matcher}
}
 
// WithTransform applies the `transform` to the actual value and matches it against `matcher`.
// The given transform must be either a function of one parameter that returns one value or a
// function of one parameter that returns two values, where the second value must be of the
// error type.
//
// var plus1 = func(i int) int { return i + 1 }
// Expect(1).To(WithTransform(plus1, Equal(2))
//
//  var failingplus1 = func(i int) (int, error) { return 42, "this does not compute" }
//  Expect(1).To(WithTransform(failingplus1, Equal(2)))
//
// And(), Or(), Not() and WithTransform() allow matchers to be composed into complex expressions.
func WithTransform(transform interface{}, matcher types.GomegaMatcher) types.GomegaMatcher {
   return matchers.NewWithTransformMatcher(transform, matcher)
}
 
// Satisfy matches the actual value against the `predicate` function.
// The given predicate must be a function of one paramter that returns bool.
//
// var isEven = func(i int) bool { return i%2 == 0 }
// Expect(2).To(Satisfy(isEven))
func Satisfy(predicate interface{}) types.GomegaMatcher {
   return matchers.NewSatisfyMatcher(predicate)
}

我们可以自己实际的情况来选择最终的预期结果来进行判断,如预期的结果是一个比较复杂的结构体对象,则可以使用Equal方法来判断两个结构体是否相当,而Equal中实际使用的则是reflect.DeepEqual方法

(四)循环判断

在使用的测试用例的编写过程中,我们可能会遇到需要循环去获取集群中的某个资源的状态,并在预期的时间内达到预期状态,我们仍然可以通过gomega包中的一些方法来实现

Eventually:循环执行传入的函数,根据传入的时间间隔和超时时间循环执行并再超过超时时间后失败,判断传入函数是否复合matcher

EventuallyWithOffset:循环执行传入的函数,根据传入的时间间隔和超时时间循环执行并再超过超时时间后失败,判断传入函数是否复合matcher,等价于Eventually(...).WithOffset(...)

Consistently:和Eventually使用方式相同,但是支持异步判断

ConsistentlyWithOffset:与Consistently使用方式相同等价于Consistently(...).WithOffset(...)

demo:

//持续获取book资源直到获取资源的error为nil
Eventually(func() error{
   return k8sClient.Get(context.TODO(), types.NamespacedName{
      Name:      Name,
      Namespace: Namespace,
   }, &demov1.Demobook{})
},time.Second).Should(BeNil())

本次分享就到这里啦~

版权声明:本文由神州数码云基地团队整理撰写,若转载请注明出处。

公众号搜索神州数码云基地,了解更多技术干货。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/22693.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

原神服务端搭建架设Centos系统

原神服务端搭建架设Centos系统 我是艾西&#xff0c;今天为大家带来原神服务端centos系统的教程 Step1. 准备工具 这个端在Windows、Linux系统上都可以跑&#xff0c;本次教程基于Linux。 准备如下工具&#xff1a; 服务器1台 centos7 系统 最低配置32核32G 公网联机 2. 手…

动态规划问题实验:数塔问题

目录 前言实验内容实验流程实验过程实验分析伪代码代码实现分析算法复杂度用例测试 总结 前言 动态规划是一种解决复杂问题的方法&#xff0c;它将一个问题分解为若干个子问题&#xff0c;然后从最简单的子问题开始求解&#xff0c;逐步推导出更复杂的子问题的解&#xff0c;最…

设计原则-单一职责原则

在编程大环境中&#xff0c;评价代码组织方式质量的好坏涉及到各个方面&#xff0c;如代码的可读性、可维护性、可复用性、稳定性等各个方面。而在面向对象语言中也可以通过以下各个方面&#xff1a; 类中方法的设计类中属性的设计类(接口、抽象类、普通类)的设计类与类之间的…

十万条数据,后端不分页咋办!(如何优化长列表渲染)

十万条数据&#xff0c;后端不分页咋办&#xff01;&#xff08;如何优化长列表渲染&#xff09; 长列表是什么&#xff1f; 我们通常把一组数量级很大的数据叫做长列表&#xff0c;比如渲染一组上千条的数据&#xff0c;我们以数组的形式拿到这些信息&#xff0c;然后遍历渲…

正点原子ALPHA开发板核心资源分析

目录 正点原子ALPHA开发板核心资源分析I.MX6ULL实物图对比SOC 主控芯片&#xff08;MCIMX6Y2CVM08AB&#xff09;NAND FLASHEMMCDDR3L 正点原子ALPHA开发板核心资源分析 I.MX6ULL实物图对比 I.MX6ULL NAND BTB 接口核心板资源图与 I.MX6ULL EMMC BTB 接口核心板资源图如上图&a…

电商项目9:新增商品

电商项目9&#xff1a;新增商品 1、前端1.1、修复前端组件通信问题1.2、引入其他前端代码1.3、会员等级列表1.4、当前分类关联的所有品牌 2、后端2.1、会员系统搭建&#xff08;注册与发现&#xff09;2.2、当前分类关联的所有品牌2.3、获取分类下所有分组&关联属性 1、前端…

shell sed命令

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 sed 命令sed 编辑器sed 的工作流程的三个过程命定格式常用选项常用操作 实验操作打印内容使用地址删除行替换插入 sed 命令 sed 编辑器 sed是一种流编辑器&#x…

听我一句劝,别去外包,干了6年,废了....

先说一下自己的情况&#xff0c;大专生&#xff0c;18年通过校招进入湖南某软件公司&#xff0c;干了接近6年的功能测试&#xff0c;今年年初&#xff0c;感觉自己不能够在这样下去了&#xff0c;长时间呆在一个舒适的环境会让一个人堕落!而我已经在一个企业干了6年的功能测试&…

中国移动董宁:深耕区块链的第八年,我仍期待挑战丨对话MVP

区块链技术对于多数人来说还是“新鲜”的代名词时&#xff0c;董宁已经成为这项技术的老朋友。 董宁2015年进入区块链领域&#xff0c;现任中国移动研究院技术总监、区块链首席专家。作为“老友”&#xff0c;董宁见证了区块链技术多个爆发式增长和平稳发展的阶段&#xff0c;…

Doxygen 源码分析: SymbolMap类

2023-05-21 10:59:35 ChrisZZ imzhuofoxmailcom Hompage https://github.com/zchrissirhcz 文章目录 1. Doxygen 版本2. SymbolMap 类概要3. 添加符号: SymbolMap<T>::add()4. 删除符号: SymbolMap<T>::remove()5. 符号查找: SymbolMap<T>::find()6. 哪里用了…

什么是半实物仿真平台自动驾驶半实物仿真平台有哪些?

文章目录 半实物仿真平台介绍自动驾驶半实物仿真平台介绍1.CARLA2.AirSim3.LGSVL Simulator 半实物仿真平台介绍 半实物仿真平台是一种综合利用虚拟仿真和实际硬件设备的仿真系统。它将虚拟环境和真实硬件设备结合起来&#xff0c;旨在提供更真实、更准确的仿真体验。 在半实…

基于html+css的图展示90

准备项目 项目开发工具 Visual Studio Code 1.44.2 版本: 1.44.2 提交: ff915844119ce9485abfe8aa9076ec76b5300ddd 日期: 2020-04-16T16:36:23.138Z Electron: 7.1.11 Chrome: 78.0.3904.130 Node.js: 12.8.1 V8: 7.8.279.23-electron.0 OS: Windows_NT x64 10.0.19044 项目…

Boundary IoU:Improving Object-Centric Image Segmentation Evaluation总结笔记

Boundary IoU:Improving Object-Centric Image Segmentation Evaluation&#xff08;边界Iou&#xff1a;改进以对象为中心的图像分割评价&#xff09; 目录 一、论文出发点 二、论文核心思想 三、相关工作 四、敏感度分析 五、Boundary IoU定义和实验证明 六、应用 七…

基于Gabor-小波滤波深度图表面法线的特征提取算法【通过正常Gabor-小波的直方图进行2D或3D特征提取】研究(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

Redis+Lua脚本防止超卖

超卖就是因为查询库存和扣减库存两个操作不是原子性操作&#xff0c;通过rua脚本执行这两个操作可以保证这两个操作原子性 判断库存量是不是大于等于1&#xff0c;如果大于等于1对库存减1&#xff0c;否则就不去减库存 StringBuilder sb new StringBuilder();sb.append("…

【JAVA进阶】Stream流

&#x1f4c3;个人主页&#xff1a;个人主页 &#x1f525;系列专栏&#xff1a;JAVASE基础 目录 1.Stream流的概述 2.Stream流的获取 3.Stream流的常用方法 1.Stream流的概述 什么是Stream流&#xff1f; 在Java 8中&#xff0c;得益于Lambda所带来的函数式编程&#xff0…

使用go语言构建区块链 Part2.工作量证明

英文源地址 简介 在上一篇文章中, 我们构建了一个非常简单的数据结构, 这是区块链数据库的本质.并且我们可以通过它们之间的链式关系来添加区块: 每个区块都链接到前一个区块.哎, 我们的区块链实现有一个重大缺陷: 向链中添加区块既容易又便捷. 区块链和比特币的关键之一是增…

面对当下各种不确定性,如何面对,每天很忙碌,不慌

&#xff08;点击即可收听&#xff09; 疫情时期,都难,疫情之后,发现还更难 随着互联网的热度的下降,各大小公司纷纷勒紧裤腰带,受打击最大的无疑是底层打工人 每天一打开手机,会发现,一些大厂裁员信息霸榜头条,年龄也是一道坎 刚刚看到一个大v发的&#xff1a; 一个原先是跨国…

如何在 OpenSUSE 上安装 VirtualBox 7?

VirtualBox 是一款开源的虚拟化软件&#xff0c;允许用户在单个计算机上运行多个操作系统。本文将详细介绍如何在 OpenSUSE 上安装 VirtualBox 7。以下是安装过程的步骤&#xff1a; 步骤一&#xff1a;下载 VirtualBox 7 首先&#xff0c;我们需要下载 VirtualBox 7 的安装包…

真题详解(语法分析输入记号流)-软件设计(八十)

真题详解&#xff08;求叶子结点数&#xff09;-软件设计&#xff08;七十九)https://blog.csdn.net/ke1ying/article/details/130787349?spm1001.2014.3001.5501 极限编程XP最佳实践&#xff1a; 测试先行、 按日甚至按小时为客户提供可运行的版本。 组件图的 插座 和插头…