【问题标题】:Unit testing os.File.Write call单元测试 os.File.Write 调用
【发布时间】:2017-05-14 06:05:47
【问题描述】:

我想对调用 os.File.Write() 并希望 100% 覆盖率的函数进行单元测试。

此函数返回n 和一个错误。引发错误很容易。我只需要关闭文件。我怎样才能诱导没有写入错误和写入数据长度不同的值n

看起来我应该创建一个虚拟 os.File 来控制返回的错误。不幸的是, os.File 不是一个接口。

编辑:根据 PeterOS 的回答,并在仔细检查文档后,Write() 方法,无论是用于 io.Writer 还是 io.File 将始终返回长度如果err 为零,则写入切片。结果,我的问题似乎毫无意义。我学到了一些重要的东西,谢谢。我有一些代码要清理。

作为旁注,我对 100% 代码覆盖率的追求似乎受到了批评。我将尝试解释实现 100% 覆盖率的理性。我愿意讨论。

  1. 我所说的 100% 覆盖意味着 100% 的代码行由单元测试执行。我使用 Go 工具来衡量这一点。

  2. 100% 的代码覆盖率显然并不意味着 0% 的错误。如果不正确测试所有可能或关键的用例,很容易获得 100% 的代码覆盖率。

  3. 100% 覆盖率度量的危险在于它成为单元测试编写的重点和目标。单元测试的第一个目标,即发现错误,然后被推到后台。

  4. 编写单元测试以达到 100% 的覆盖率会增加开发成本,而且这样做并不有趣。我知道。

  5. 我看到的 100% 代码覆盖率的唯一好处是可以轻松检测和定位未经测试的代码添加。

收益是否超过成本取决于您的编程方式。我像画家一样编程,根据需要在这里和那里修改和添加代码。我把所有的计划和路线图都记在心里。如果我必须添加或更新测试并检查它们,我会忘记我在做什么。所以我首先对功能进行编码,然后对其进行测试。 100% 的代码覆盖率让我很容易找到需要添加测试的代码添加。这是一个启发式。不是检测所有缺失测试的方法。

结论:不要混淆 100% 的代码覆盖率和 0% 的错误确实很重要。同样重要的是要意识到,以 100% 的代码覆盖率为目标可能会将测试的第一个目标传递给后台,即发现错误。最后,达到 100% 覆盖率的成本必须与其好处相平衡,即轻松检测和定位未经测试的代码,否则会浪费资源。根据您的个人开发方法做出选择。我自己做的。

【问题讨论】:

  • 这是个好问题,但100% coverage 既不现实,也没有意义。
  • @Flimzy 我想要 100% 的覆盖率有多个正当理由。这不是重点。
  • @chmike:我知道这不是重点。这就是为什么我说这是一个很好的问题并投了赞成票。但是不,没有任何正当理由想要这样做,因为 100% 的测试覆盖率在逻辑上是不可能的,想要不可能的事情永远不会成立。
  • @Flimzy 正如 PeterOS 所指出的,当 os.File.Write 返回 nil 错误时,n 是切片的长度。所以测试 n 没有意义。我会删除问题。感谢您花时间尝试帮助我。

标签: unit-testing go


【解决方案1】:

您需要自己将 os.File 抽象为一个接口。

虽然 100% 的覆盖率很好,但一旦你有一个合理大小的程序,这总是一个遥不可及的目标

【讨论】:

  • "这总是一个遥不可及的目标" 不仅如此,而且 100% 的覆盖率实际上是不可能的。大多数工具测量“行覆盖率”或“语句覆盖率”,这些指标 100% 是可能的,但也毫无意义,因为大多数行都有多种执行可能性。
【解决方案2】:

为了有用,程序应该是正确的、可维护的、可读的和相当有效的。代码覆盖率测试通常会提供一种错误的安全感。 100% 的覆盖率并不表示没有错误。

阅读 OP (@chmike) 发布的 code 作为他的问题的答案很有趣。

import (
    "os"
    "github.com/pkg/errors"
)

var invalidN bool // initalized to false

func Append(f *os.File, data []byte) error {
    n, err := f.Write(data)
    if err != nil {
        return errors.Wrapf(err, "failed appending")
    }
    if n != len(data) || invalidN {
        return errors.Error("failed appending")
    }
    // ...
    return nil 
}

代码未能通过最基本的覆盖测试。它无法编译:undefined: errors.Error

除其他事项外,OP 还问:我怎样才能得出...一个与写入数据长度不同的值 n? OP的答案没有这样做。如果确实发生了这种情况,那么f.Write(data) 将返回err = io.ErrShortWrite。因此,err != nil 和测试n != len(data) || invalidN 将不会执行。因此,具有讽刺意味的是,覆盖测试的覆盖测试将失败。

// io.ErrShortWrite means that a write accepted fewer bytes than requested
// but failed to return an explicit error.
var ErrShortWrite = errors.New("short write")

func (f *File) Write(b []byte) (n int, err error) {
    // ...
    if n != len(b) {
        err = io.ErrShortWrite
    }
    // ...
    return n, err
}

如果您希望您的代码正确无误,请不要依赖覆盖测试。覆盖率测试付出了很多努力,但收效甚微。有更好更有效的方法来确保程序的正确性。

【讨论】:

  • 我更正了代码并将errors.Error替换为ˋerror.New`。这应该解决你的第一点。我们显然不同意 100% 覆盖率测试的相关性。那不是重点。您对返回的错误所说的内容未记录在标准库中。您可能已经从实现中推断出这一点,但这种假设可能不适用于其他实现。
  • 糟糕,抱歉。没有正确阅读 stdlib 文档。事实上,在正常情况下,这个测试永远不会是真的。我会删除那个问题。
【解决方案3】:

我用这个替换了之前的答案。

documentation of os.File.Write() 声明如下

当 n != len(b) 时,Write 会返回一个非 nil 错误。

因此在err == nil 时检查n == len(b) 是没有意义的。这些指令已从代码中删除,无需通过单元测试来覆盖这种情况以达到 100% 的代码覆盖率。

【讨论】:

  • 随着 PeterOS 不断删除我的 cmets,我将在这里发表评论。他的回答有很多问题。 1)他表达了他对 100% 的意见,并认为我在我提供的示例代码中犯的错误作为他的观点的相关理由。编译器会检测到该错误。这不是 100% 覆盖率的失败。 2) 他声称如果 n != len(b) 覆盖将失败。现实情况是,除非 invalidN 为真,否则此条件永远不会为真。这就是我获得 100% 覆盖率的方式。 3)他提供了实现代码,他应该参考规范。等
猜你喜欢
  • 2012-03-06
  • 2018-08-06
  • 2023-03-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-11-25
相关资源
最近更新 更多