单元测试 os.File.Write 调用

huangapple go评论105阅读模式
英文:

Unit testing os.File.Write call

问题

我想对调用os.File.Write()的函数进行单元测试,并且要实现100%的覆盖率。

这个函数返回n和一个错误。引发一个错误很容易,只需要关闭文件即可。我如何在不引发写入错误的情况下,使n的值与写入数据的长度不同?

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

编辑:根据PeterOS的回答,并在仔细检查了文档后,无论是对于io.Writer还是io.FileWrite()方法,如果err为nil,它都会返回写入切片的长度。因此,我的问题似乎是无意义的。我学到了一些重要的东西,谢谢。我需要清理一些代码。

顺便说一下,似乎有人对我追求100%代码覆盖率提出了批评。我将尝试解释一下我追求100%覆盖率的理由。我愿意进行讨论。

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

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

  3. 100%覆盖率指标的危险在于它成为单元测试编写的焦点和目标,而第一个目标——发现错误——则被推到了后台。

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

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

利益是否超过成本取决于你的编程方式。我像一位画家一样编写代码,根据需要在各个地方进行修改和添加。我将所有的计划和路线都记在脑海中。如果我必须添加或更新测试并进行检查,我会迷失在自己的思路中。所以我首先编写功能,然后进行测试。100%的代码覆盖率对我来说非常简单,可以定位到我需要添加测试的代码添加部分。这是一种启发式方法,而不是检测所有缺失测试的方法。

结论:确实很重要的是不要将100%代码覆盖率与0%错误混淆。同样重要的是要意识到追求100%代码覆盖率可能会使测试的第一个目标——发现错误——变得次要。最后,达到100%覆盖率是有成本的,必须权衡其能够轻松检测和定位未经测试的代码的好处,否则就是资源的浪费。根据个人的开发方法选择适合自己的方式。我已经做出了我的选择。

英文:

I want to unit test a function that calls os.File.Write() and want 100% coverage.

This function returns n and an error. Inducing an error is easy. All I need is to close the file. How can I induce no write error and a value n different of the written data length ?

It looks like I should create a dummy os.File on which I can control the error returned. Unfortunately, os.File is not an interface.

Edit: Based on the answer of PeterOS, and after double checking the documentation, the Write() method, whether is for the io.Writer or the io.File will always return the length of the written slice if err is nil. As a consequence, it appears that my question is pointless. I learned something important, thanks. I have some code to cleanup.

As a side note, it seems that my quest of the 100% code coverage is criticized. I'll try to explain my rational to achieve 100% coverage. I'm open to discussion.

  1. the 100% coverage I'm talking about means that 100% of the code lines are executed by the unit tests. I use the Go tools to measure that.

  2. 100% code coverage does obviously not mean 0% bugs. It is easy to get 100% code coverage without properly testing all possible or critical use cases.

  3. the danger of the 100% coverage metric is that it becomes the focus and goal of the unit tests writing. The first goal of unit testing, which is to find bugs, is then pushed in the background.

  4. writing unit tests to reach 100% coverage adds a significant cost to development and it's not fun to do. I know that.

  5. the only benefit I see in 100% code coverage is to make it easy to detect and locate untested code addition.

Whether the benefit beats the costs depends on the way you program. I program like a painter modifying and adding code here and there as needed. I keep all the plan and road map in my head. If I had to add or update tests and check them, I would loose track of what I was doing. So I first code the feature and then test it. The 100% code coverage make it very simple for me to locate the code addition for which I need to add tests. It's a heuristic. Not a method to detect all missing tests.

Conclusion: it is indeed important to not confuse 100% code coverage with 0% bugs. It is also important to be aware that targeting 100% code coverage may pass to the background the first goal of testing which is to find bugs. Finally, reaching 100% coverage has a cost that must be balanced by its benefit which is to easily detect and locate untested code, otherwise it's a waste of resource. Make your pick based on your personal development methodology. I made mine.

答案1

得分: 4

你需要自己将os.File抽象成一个接口。

虽然100%的覆盖率很好,但对于一个相对较大的程序来说,这总是一个难以达到的目标。

英文:

You will need to abstract the os.File into an interface yourself.

Although 100% coverage is nice, it's always an out of reach goal, once you have anything like a reasonable sized program

答案2

得分: 1

为了有用,程序应该是正确的、可维护的、可读的和相对高效的。代码覆盖测试通常会给人一种虚假的安全感。100%的覆盖率并不意味着没有错误。

有趣的是阅读 OP(@chmike)在他的问题中发布的答案的代码。

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

var invalidN bool // 初始化为 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 表示写入的字节数少于请求的字节数,但未返回明确的错误。
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
}

如果你希望你的代码是正确的,不要依赖覆盖测试。覆盖测试需要很大的努力,但效益很小。有更好、更高效的方法来确保程序的正确性。

英文:

To be useful, programs should be correct, maintainable, readable, and reasonably efficient. Code coverage testing typically provides a false sense of security. 100% coverage does not indicate the absence of bugs.

It's interesting to read the code that the OP (@chmike) posted as his answer to his question.

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 
}

The code fails the most elemental coverage test. It does not compile: undefined: errors.Error.

Amongst other things, the OP asks: How can I induce ... a value n different of the written data length? The OP's answer does not do that. If that does occur then f.Write(data) will return err = io.ErrShortWrite. Therefore, err != nil and the test n != len(data) || invalidN will not execute. So, ironically, a coverage test of the coverage test will fail.

// 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
}

If you want your code to be correct, don't rely on coverage tests. Coverage tests are a lot of effort for little benefit. There are better and more efficient ways to ensure program correctness.

答案3

得分: -1

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

os.File.Write()文档中说明了以下内容:

> 当 n != len(b) 时,Write 返回一个非空的错误。

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

英文:

I replaced the previous answer with this one.

The documentation of os.File.Write() states the following

> Write returns a non-nil error when n != len(b).

It is thus pointless to check if n == len(b) when err == nil. These instructions are removed from the code and there is no need to cover this case with unit tests to reach 100% code coverage.

huangapple
  • 本文由 发表于 2017年5月11日 20:51:57
  • 转载请务必保留本文链接:https://go.coder-hub.com/43916000.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定