2

Custom class has file Write and Read methods. How can the unit test for write be properly validated if the only way to get the file contents is to call the read method?

Adam
  • 1,613
  • 1
  • 16
  • 24
  • for what language? However to achieve you would have to use mocks. see http://stackoverflow.com/questions/2665812/what-is-mocking – Honey Jan 10 '17 at 17:56
  • 1
    1. That's integration test, not unit test, as you "use the code you don't control" (i.e. file system). 2. In case of any persistence, a round trip test is pretty common practice - you call write method, then you call read and check that you received what you wrote. Of course you could cheat and keep it in memory, but tests are meant to help development and you can never protect yourself against everything. But if you really want you can also check that the file was created. – Jaroslaw Pawlak Jan 10 '17 at 17:58
  • @Honey Never mock the code you don't control. The actual implementation might not do what you think it does. Rule of thumb is to mock only something for which you have your unit/integration tests. – Jaroslaw Pawlak Jan 10 '17 at 17:59
  • @JaroslawPawlak 1. "Never mock the code you don't control" – Honey Jan 10 '17 at 18:01
  • I'm using C# and NUnit, didn't specify them because this is a problem that will be encountered in all languages. What would be mocked, I need to validate that the write method wrote the expected contents. – Adam Jan 10 '17 at 18:05
  • @Jaroslaw Pawlak can you elaborate on "Never mock the code you don't control." The topic linked to by Honey says that you should always mock units that are not part of your application. For example if you make network calls you should mock the response codes because it is not your responsibility to test the network calls. – Adam Jan 10 '17 at 18:07
  • @Honey Mocks won't help you here. If you mock Write() then what is Read() reading? If you mock Read() then how do you read what Write() wrote? If you mock both, what are you testing? – Schwern Jan 10 '17 at 19:58
  • @Schwern *If you mock Write() then what is Read() reading?* I don't understand you. I said mock `write()` as in to *inject* `read()`. – Honey Jan 10 '17 at 20:23
  • 1
    @Honey Sorry, I don't follow. read() and write() are in the same class, so I'm not sure why we need injection, what an injected read() would do, and how it would be used to test write() without also modifying write() such that all you're testing is mocked code. I don't have any experience with injection, maybe provide it as an answer? – Schwern Jan 10 '17 at 21:50
  • 1
    @Schwern I'm still learning TDD, but I see what you're saying that if they are in the same class, then mocking doesn't make any sense. Thank you – Honey Jan 10 '17 at 21:51
  • @Adam "you should always mock units that are not part of your application" - no, absolutely not. You should never mock another application (unless this is an application that you wrote, and you have tests for it). If you write a test where you mock and say `when I hit stackoverflow.com I get back 404` - how do you know that this is what actually happens? Instead of unit test, you should have integration test and actually hit stackoverflow.com. Then if the response changes, your test will detect it, while in case of mocking you would have a bug. – Jaroslaw Pawlak Jan 11 '17 at 14:25

1 Answers1

3
  1. Test Read() first using pre-existing test files.
  2. Now that you know it works, use Read() to test Write().

Best Practices are less about being the best practice and more about avoiding bad practices. Bad practices like coupling too much code together in a single test such that you can't tell where the problem lies and such that multiple bugs can interact to cause failure. Unit tests, among other purposes, try to avoid coupling. But if you try to follow Best Practices as dogma, and concern yourself too much with what category of test everything is, you'll wind up doing some bad practices just to try and follow the Best Practices.

Reading and writing files are inherently coupled, writing isn't much use unless you can read it, and you have them in the same class so it's not an external dependency. The trick is to write your tests such that Read() and Write() bugs can't interact to cause a false positive. For example, if you did something like this:

file = Temp.file
content = Read(file)
Write(file, content)

assert_eq( Read(file), content )

Then if Read(file) has a bug which occasionally returns an empty string, and Write(file, content) has a bug which just wipes the file, you'll get a false positive from the assert. Whereas if you wrote it like so:

file = Temp.file
content = "The quick brown fox jumped over the lazy grey dog.\n"
Write(file, content)
assert_eq( Read(file), content )

Bugs in Read() and Write() cannot lead to a false positive (unless you're in a pass-by-reference language, in which case content should be declared constant). Though they can interact, which leads us to the next bit.


You can test Read() without Write(), so do that first to be assured Read() works before using it to test Write(). Pre-generate a bunch of interesting files to exercise Read(): Unicode, empty files, big files, special files, etc...

# Pardon my mish-mash prototype language
tests = {
    empty: "",
    just_newline: "\n",
    simple: "This test is not metal.",
    simple_unicode: "†Hîß †É߆ Îß µé†å¬¡¡¡ "
}

for (file, want) in tests {
    assert_eq( Read(file), want, "Read(#{file})" )
}

If your test suite features ordering, make sure the Read() tests run before the Write() tests. Now if you run your tests and Write() fails you can be fairly certain it's not because Read() failed.

Once you're satisfied Read() works, you can safely use it to test Write().


There are always couplings in your code and tests, without them you can't get anything done. For example, if Temp.file has a bug where it doesn't always an empty temp file that can introduce bugs.

The trick isn't to eliminate all couplings, the trick is to minimize them. With regard to testing, strive to test one thing at a time. Since the Temp class was presumably well-tested when it was installed, and because it's a standard and very well used class, it's safe to rely on it in your tests. In this way you build up an ever-increasing foundation of well tested code to build and test more code on top of.

Schwern
  • 127,817
  • 21
  • 150
  • 290