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.
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.
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.
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.
writing unit tests to reach 100% coverage adds a significant cost to development and it's not fun to do. I know that.
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.
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.
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
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.