metrics-server源码中的测试替身:Mock与Fake实现

metrics-server源码中的测试替身:Mock与Fake实现

【免费下载链接】metrics-server Scalable and efficient source of container resource metrics for Kubernetes built-in autoscaling pipelines. 【免费下载链接】metrics-server 项目地址: https://gitcode.com/gh_mirrors/me/metrics-server

在软件开发领域,单元测试是保障代码质量的关键环节。然而,当代码依赖外部系统或组件时,直接进行测试变得困难重重。测试替身(Test Double)正是解决这一难题的有效手段。本文将深入剖析metrics-server源码中两种常用的测试替身——Mock与Fake的实现方式,带您领略测试驱动开发的精髓。

测试替身概述

测试替身是指在测试过程中,用来替代真实对象的特殊对象。它们可以模拟真实对象的行为,使测试更加可控、高效。在metrics-server项目中,主要使用了两种测试替身:Mock和Fake。

Mock与Fake的区别

类型特点适用场景
Mock专注于验证交互行为,通常只实现必要的方法测试组件间的通信协议
Fake是真实实现的简化版本,具有完整的功能逻辑测试复杂业务逻辑

测试替身的优势

  1. 隔离性:将被测试代码与外部依赖隔离,确保测试结果的准确性
  2. 可控性:可以精确控制测试环境,复现各种边界情况
  3. 效率:避免启动外部服务,大幅提升测试速度
  4. 覆盖率:能够模拟异常场景,提高代码覆盖率

metrics-server中的Fake实现

Fake是真实实现的简化版本,它包含完整的业务逻辑,但通常会省略一些复杂的功能,如网络通信、持久化存储等。在metrics-server中,Fake主要用于模拟Kubernetes API和Kubelet客户端。

fakeKubeletClient:模拟Kubelet客户端

pkg/scraper/scraper_test.go文件中,我们发现了一个名为fakeKubeletClient的结构体,它实现了client.KubeletMetricsGetter接口:

type fakeKubeletClient struct {
    delay        map[*corev1.Node]time.Duration
    metrics      map[*corev1.Node]*storage.MetricsBatch
    defaultDelay time.Duration
}

var _ client.KubeletMetricsGetter = (*fakeKubeletClient)(nil)

func (c *fakeKubeletClient) GetMetrics(ctx context.Context, node *corev1.Node) (*storage.MetricsBatch, error) {
    delay, ok := c.delay[node]
    if !ok {
        delay = c.defaultDelay
    }
    metrics, ok := c.metrics[node]
    if !ok {
        return nil, fmt.Errorf("Unknown node %q", node.Name)
    }

    select {
    case <-ctx.Done():
        return nil, fmt.Errorf("timed out")
    case <-time.After(delay):
    }
    return metrics, nil
}

这个Fake实现具有以下特点:

  1. 模拟网络延迟:通过delay字段可以为不同节点设置不同的响应延迟
  2. 预设返回值:使用metrics字段存储不同节点的预期返回结果
  3. 上下文支持:尊重上下文取消和超时机制

fakeNodeLister:模拟节点列表器

另一个典型的Fake实现是fakeNodeLister,它模拟了Kubernetes的节点列表功能:

type fakeNodeLister struct {
    nodes   []*corev1.Node
    listErr error
}

func (l *fakeNodeLister) List(_ labels.Selector) (ret []*corev1.Node, err error) {
    if l.listErr != nil {
        return nil, l.listErr
    }
    // NB: this is ignores selector for the moment
    return l.nodes, nil
}

func (l *fakeNodeLister) Get(name string) (*corev1.Node, error) {
    for _, node := range l.nodes {
        if node.Name == name {
            return node, nil
        }
    }
    return nil, fmt.Errorf("no such node %q", name)
}

这个Fake实现允许测试人员:

  • 预设节点列表数据
  • 模拟列表操作错误
  • 查找特定节点

Fake的应用实例

在测试用例中,Fake的使用通常分为三个步骤:初始化、配置、验证。以下是一个使用fakeKubeletClient的示例:

It("should return the results of all nodes and pods", func() {
    By("setting up client to take 1 second to complete")
    client.defaultDelay = 1 * time.Second

    By("running the scraper with a context timeout of 3*seconds")
    start := time.Now()
    scraper := NewScraper(&nodeLister, &client, 3*time.Second, labelRequirement)
    timeoutCtx, doneWithWork := context.WithTimeout(context.Background(), 4*time.Second)
    dataBatch := scraper.Scrape(timeoutCtx)
    doneWithWork()

    By("ensuring that the full time took at most 3 seconds")
    Expect(time.Since(start)).To(BeNumerically("<=", 3*time.Second))

    By("ensuring that all the nodeLister are listed")
    Expect(nodeNames(dataBatch)).To(ConsistOf([]string{"node1", "node-no-host", "node3", "node4"}))
    By("ensuring that all pods are present")
    Expect(podNames(dataBatch)).To(ConsistOf([]string{"ns1/pod1", "ns1/pod2", "ns2/pod1", "ns3/pod1"}))
})

metrics-server中的Mock实现

Mock专注于验证对象间的交互行为,它通常只实现必要的方法,并记录方法的调用情况。在metrics-server中,Mock主要用于验证组件间的通信协议。

scraperMock:验证Scraper行为

pkg/server/server_test.go文件中,我们找到了一个名为scraperMock的结构体:

type scraperMock struct {
    result *storage.MetricsBatch
    err    error
}

var _ scraper.Scraper = (*scraperMock)(nil)

func (s *scraperMock) Scrape(ctx context.Context) *storage.MetricsBatch {
    return s.result
}

这个Mock实现了scraper.Scraper接口,它的主要作用是:

  1. 返回预设的指标数据
  2. 模拟抓取过程中可能出现的错误

storageMock:验证存储交互

同样在pkg/server/server_test.go中,还有一个storageMock结构体:

type storageMock struct {
    ready bool
}

var _ storage.Storage = (*storageMock)(nil)

func (s *storageMock) Store(batch *storage.MetricsBatch) {}

func (s *storageMock) GetPodMetrics(pods ...*metav1.PartialObjectMetadata) ([]metrics.PodMetrics, error) {
    return nil, nil
}

func (s *storageMock) GetNodeMetrics(nodes ...*corev1.Node) ([]metrics.NodeMetrics, error) {
    return nil, nil
}

func (s *storageMock) Ready() bool {
    return s.ready
}

这个Mock实现了storage.Storage接口,用于验证Server与Storage之间的交互。通过设置ready字段,可以模拟存储组件的不同状态。

Mock的应用实例

以下是一个使用Mock的测试用例,它验证了Server在存储未就绪时的行为:

It("metric-storage-ready probe should fail if store is not ready", func() {
    check := server.probeMetricStorageReady("")
    Expect(check.Check(nil)).NotTo(Succeed())
})

It("metric-storage-ready probe should pass if store is ready", func() {
    store.ready = true
    check := server.probeMetricStorageReady("")
    Expect(check.Check(nil)).To(Succeed())
})

测试替身的设计模式

在metrics-server的测试代码中,我们可以发现一些反复出现的设计模式,这些模式可以帮助我们更好地理解和使用测试替身。

接口驱动设计

metrics-server广泛采用了接口驱动设计,这为测试替身的实现提供了便利。例如,scraper.Scraper接口定义了指标抓取的标准:

// 假设的接口定义,实际代码中可能有所不同
type Scraper interface {
    Scrape(ctx context.Context) *storage.MetricsBatch
}

通过依赖接口而非具体实现,我们可以轻松地在测试中替换成Mock或Fake。

依赖注入

metrics-server使用依赖注入模式,将外部依赖通过构造函数传入:

server = NewServer(nil, nil, nil, store, scraper, resolution)

这种设计使得测试时可以轻松替换真实依赖为测试替身。

测试数据构建器

为了简化测试数据的创建,metrics-server使用了测试数据构建器模式:

func makeNode(name, hostName, addr string, ready bool) *corev1.Node {
    res := &corev1.Node{
        ObjectMeta: metav1.ObjectMeta{Name: name},
        Status: corev1.NodeStatus{
            Addresses: []corev1.NodeAddress{},
            Conditions: []corev1.NodeCondition{
                {Type: corev1.NodeReady},
            },
        },
    }
    // ... 省略其他初始化代码
    return res
}

这个函数可以快速创建具有特定属性的Node对象,大大简化了测试用例的编写。

测试替身的最佳实践

基于对metrics-server源码的分析,我们可以总结出以下测试替身的最佳实践:

合理选择测试替身类型

  • 使用Fake:当需要测试复杂业务逻辑,且依赖具有明确行为时
  • 使用Mock:当只需验证交互协议,而不关心具体实现细节时

保持测试替身的简洁性

测试替身应该只实现必要的功能,避免引入复杂的逻辑。例如,scraperMock只返回预设的结果,不包含任何实际的抓取逻辑。

明确测试意图

每个测试用例应该有明确的测试意图,测试替身的使用应该服务于这个意图。例如,在测试超时逻辑时,fakeKubeletClientdelay字段被用来模拟网络延迟。

验证边界条件

测试替身非常适合验证边界条件,如:

  • 网络超时
  • 资源不可用
  • 数据格式错误

在metrics-server中,我们可以看到大量使用Fake来模拟这些场景的测试用例。

总结

通过对metrics-server源码的分析,我们深入了解了Mock和Fake这两种测试替身的实现方式和应用场景。这些测试替身不仅提高了测试效率,还确保了测试的可靠性和可重复性。

主要收获

  1. 接口设计的重要性:清晰的接口定义是使用测试替身的前提
  2. 测试隔离:通过测试替身将被测试代码与外部依赖隔离
  3. 场景覆盖:利用测试替身可以轻松模拟各种复杂场景
  4. 代码质量:完善的测试替身体系有助于提高代码质量和可维护性

未来展望

随着metrics-server项目的不断发展,测试替身的使用可能会更加广泛和深入。我们可以期待看到:

  • 更多自动化生成的Mock(如使用gomock)
  • 更复杂的Fake实现,支持更多测试场景
  • 测试替身的复用机制,减少重复代码

测试替身是编写高质量单元测试的关键技术之一。通过学习metrics-server项目中的实现方式,我们可以更好地在自己的项目中应用这一技术,提高代码质量,降低维护成本。

希望本文能够帮助您深入理解测试替身的概念和实践,为您的测试工作带来启发和帮助。如果您对metrics-server的测试代码有更深入的研究,欢迎分享您的发现和见解!

【免费下载链接】metrics-server Scalable and efficient source of container resource metrics for Kubernetes built-in autoscaling pipelines. 【免费下载链接】metrics-server 项目地址: https://gitcode.com/gh_mirrors/me/metrics-server

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值