-2

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.

Jonathan Hall
  • 75,165
  • 16
  • 143
  • 189
chmike
  • 20,922
  • 21
  • 83
  • 106
  • 2
    It's a good question, but `100% coverage` is neither realistic, nor meaningful. – Jonathan Hall May 11 '17 at 13:03
  • Related: [Example code for testing the filesystem in Golang](http://stackoverflow.com/questions/43912124/example-code-for-testing-the-filesystem-in-golang). – icza May 11 '17 at 13:04
  • @Flimzy There are multiple valid reason why I want 100% coverage. That is not the point. – chmike May 11 '17 at 13:04
  • 2
    @chmike: I know that's not the point. That's why I said it's a good question and upvoted. But no, there aren't any valid reasons to want that, because 100% test coverage is a logical impossibility, and wanting the impossible is never valid. – Jonathan Hall May 11 '17 at 13:05
  • 1
    @Flimzy as pointed out by PeterOS, when os.File.Write returns a nil error, n is the length of the slice. So there is no point in testing n. I'll delete the question. Thanks for taking the time to try help me. – chmike May 11 '17 at 17:31
  • @chmike: Oh, indeed I missed that detail myself. I think the general question of "How do I make `os.File.Write()` fail for tests?" is still valid, though. – Jonathan Hall May 11 '17 at 19:47

3 Answers3

4

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

Neil
  • 11,059
  • 3
  • 31
  • 56
  • 2
    "it's always an out of reach goal" Not only that, but 100% coverage is a literal impossibility. Most tools measure "line coverage", or "statement coverage" and 100% of those metrics is possible, but also meaningless, because most lines have multiple execution possibilities. – Jonathan Hall May 11 '17 at 13:10
1

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.

Community
  • 1
  • 1
peterSO
  • 158,998
  • 31
  • 281
  • 276
  • I corrected the code and replaced `errors.Error` by ˋerror.New`. That should address your first point. We obviously disagree on the pertinence of 100% coverage test. That is not the point. What you say about the returned error is not documented in the stdlib. You may have deduced this from the implementation, but this assumption may not be valid with other implementation. – chmike May 11 '17 at 17:17
  • Oops, sorry. Didn't read the stdlib documentation properly. Indeed, under normal condition, this test can never be true. I'll delete that question. – chmike May 11 '17 at 17:28
-1

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.

chmike
  • 20,922
  • 21
  • 83
  • 106
  • As PeterOS keep deleting my comments, I'll comment here. There are many problems with his answer. 1) He express his opinion on 100%, and consider the error I made in the example code I provided as a pertinent justification of his point. The error would have been detected by the compiler. It is not a failure of 100% coverage. 2) he claim that the coverage would fail if n != len(b). The reality is that this condition will never be true unless invalidN is true. This is how I get 100% coverage. 3) He provide implementation code where he should refer to specification instead. etc. – chmike May 17 '17 at 08:47