Writing all these tests I sometimes felt that the changes between two days was not significant. And sometimes I felt there were versions that did not make it into this advent calendar. But I stuck with the versions you have seen since I think they represent a quite natural evolution of the initial test and hence I have not presented any side tracks that eventually turn into a dead end because I wanted to progress toward the final version which is the one I like most of all these versions. Also I had to be a little bit puristic and let even the smallest changes result in a version by it self rather than merging a number of small changes into a single version change in order get 24 different versions. There are however a few of the versions that I think are worth a special mentioning.
Please leave a comment and let me know what your favorite version in this series of test is!
1: public class Advent12 : IDisposable 2: { 3: private IFileUtil m_file; 4: 5: private void SetUp(string content) 6: { 7: m_file = new FileUtil("SomeFile.txt"); 8: m_file.Create(content); 9: } 10: 11: public void Dispose() 12: { 13: m_file.Delete(); 14: } 15: 16: [Fact] 17: public void TestReadReadableFile() 18: { 19: SetUp("CONTENT"); 20: string content = m_file.Read(); 21: Assert.Equal<string>("CONTENT", content); 22: } 23: 24: [Fact] 25: public void TestReadUnreadableFile() 26: { 27: SetUp("SHOULD NOT BE ABLE TO READ THIS"); 28: m_file.Readable = false; 29: Assert.Throws<AccessViolationException>(() => { m_file.Read(); }); 30: } 31: }
Since the setup-method is called explicitly, why not have several setup-methods for different kinds of setup?
1: public class Advent5 : IDisposable 2: { 3: private IFileUtil m_file; 4: 5: public void Dispose() 6: { 7: m_file.Delete(); 8: } 9: 10: [Fact] 11: public void TestReadOK() 12: { 13: // Create a test file 14: m_file = new FileUtil("SomeFile.txt"); 15: m_file.Create("CONTENT"); 16: 17: // Should be able to read file 18: Assert.DoesNotThrow(() => { m_file.Read(); }); 19: } 20: 21: [Fact] 22: public void TestReadFails() 23: { 24: // Create a test file 25: m_file = new FileUtil("SomeFile.txt"); 26: m_file.Create("CONTENT"); 27: 28: m_file.Readable = false; 29: 30: // Should NOT be able to read file. 31: Assert.Throws<AccessViolationException>(() => { m_file.Read(); }); 32: } 33: }
Now we're using Xunit-net's clean up mechanism, the IDisposableinterface. We still got some redundant code in the setup part of each test. Next I'll refactor that.
1: public class Advent17 2: { 3: private FileUtilWithDelete SetUp(string content, bool readable) 4: { 5: FileUtilWithDelete file = new FileUtilWithDelete("SomeFile.txt"); 6: file.Create(content); 7: file.Readable = readable; 8: return file; 9: } 10: 11: [Fact] 12: public void TestReadReadableFileReturnsFileContent() 13: { 14: using (FileUtilWithDelete file = SetUp("CONTENT", true)) 15: { 16: string content = file.Read(); 17: Assert.Equal<string>("CONTENT", content); 18: } 19: } 20: 21: [Fact] 22: public void TestReadUnreadableFileThrowsCorrectException() 23: { 24: using (FileUtilWithDelete file = SetUp("SHOULD NOT BE ABLE TO READ THIS", false)) 25: { 26: Assert.Throws<AccessViolationException>(() => { file.Read(); }); 27: } 28: } 29: }
Starting all test methods with "Test" does not really add any value to the name either. Naming the tests something that sounds like a real sentence is something I prefer.
1: public class Advent18 2: { 3: private FileUtilWithDelete SetUp(string content, bool readable) 4: { 5: FileUtilWithDelete file = new FileUtilWithDelete("SomeFile.txt"); 6: file.Create(content); 7: file.Readable = readable; 8: return file; 9: } 10: 11: [Fact] 12: public void ReadingAReadableFileReturnsFileContent() 13: { 14: using (FileUtilWithDelete file = SetUp("CONTENT", true)) 15: { 16: string content = file.Read(); 17: Assert.Equal<string>("CONTENT", content); 18: } 19: } 20: 21: [Fact] 22: public void ReadingAnUnreadableFileThrowsCorrectException() 23: { 24: using (FileUtilWithDelete file = SetUp("SHOULD NOT BE ABLE TO READ THIS", false)) 25: { 26: Assert.Throws<AccessViolationException>(() => { file.Read(); }); 27: } 28: } 29: }
Now I like the names but they are hard to read. Why not add some space?
1: public class Advent1 2: { 3: [Fact] 4: public void TestRead() 5: { 6: // Create a test file 7: IFileUtil file = new FileUtil("SomeFile.txt"); 8: file.Create("CONTENT"); 9: 10: // Should be able to read file 11: try 12: { 13: file.Read(); 14: } 15: catch (Exception) 16: { 17: throw new AssertException("Unexpected exception thrown"); 18: } 19: 20: file.Readable = false; 21: 22: // Should NOT be able to read file. 23: try 24: { 25: file.Read(); 26: throw new AssertException("No exception thrown"); 27: } 28: catch (AccessViolationException) 29: { 30: } 31: 32: // Clean up 33: file.Delete(); 34: } 35: }
With this first version of the test there I have not utilized all the features of Xunit.net. I basically do all the verification my self and create test failures by explicitly throwing AssertFailure exceptions. There are several problems with this code but the only problem that will be addressed in the next version of this test is if line 25 throws another exception than AccessViolationException. Xunit.net will save the day but it is quite ugly So in tomorrows version we'll fix that.
1: public class Advent2 2: { 3: [Fact] 4: public void TestRead() 5: { 6: // Create a test file 7: IFileUtil file = new FileUtil("SomeFile.txt"); 8: file.Create("CONTENT"); 9: 10: // Should be able to read file 11: try 12: { 13: file.Read(); 14: } 15: catch (Exception) 16: { 17: throw new AssertException("Unexpected exception thrown"); 18: } 19: 20: file.Readable = false; 21: 22: // Should NOT be able to read file. 23: try 24: { 25: file.Read(); 26: throw new AssertException("No exception thrown"); 27: } 28: catch (AccessViolationException) 29: { 30: } 31: catch (Exception) 32: { 33: throw new AssertException("Unexpected exception thrown"); 34: } 35: 36: // Clean up 37: file.Delete(); 38: } 39: }
Now I've added a handler for unexpected exceptions but this is still a pretty cumbersome way to write tests verifying that exceptions are thrown or not correctly. Especially since Xunit.net provides us with a really simple way to handle this for us. That is what will happen next.
1: public class Advent3 2: { 3: [Fact] 4: public void TestRead() 5: { 6: // Create a test file 7: IFileUtil file = new FileUtil("SomeFile.txt"); 8: file.Create("CONTENT"); 9: 10: // Should be able to read file 11: Assert.DoesNotThrow(() => { file.Read(); }); 12: 13: file.Readable = false; 14: 15: // Should NOT be able to read file. 16: Assert.Throws<AccessViolationException>(() => { file.Read(); }); 17: 18: // Clean up 19: file.Delete(); 20: } 21: }
Using XUnit.net asserts to verify exceptions is much better than what we did yesterday. Now the test starts to be small enough to be understood easily. But I think there is a major problem with this test. We're not only testing that we cannot read an unreadable file, I'm also verifying that I can read a readable file. I think this should be split up into two different tests and that is what will happen next.
1: public class Advent4 2: { 3: [Fact] 4: public void TestReadOK() 5: { 6: // Create a test file 7: IFileUtil file = new FileUtil("SomeFile.txt"); 8: file.Create("CONTENT"); 9: 10: // Should be able to read file 11: Assert.DoesNotThrow(() => { file.Read(); }); 12: 13: // Clean up 14: file.Delete(); 15: } 16: 17: [Fact] 18: public void TestReadFails() 19: { 20: // Create a test file 21: IFileUtil file = new FileUtil("SomeFile.txt"); 22: file.Create("CONTENT"); 23: 24: file.Readable = false; 25: 26: // Should NOT be able to read file. 27: Assert.Throws<AccessViolationException>(() => { file.Read(); }); 28: 29: // Clean up 30: file.Delete(); 31: } 32: }
Now that the initial test has been split up into two tests. One testing reading a readable file and one testing an unreadable file. Now I got some redundant code I wanna get rid of. The first thing is the clean up code. It is not run if the test fails and that is probably a bad thing. So next I'll fix that.
1: public class Advent6: IDisposable 2: { 3: private IFileUtil m_file; 4: 5: public Advent6() 6: { 7: m_file = new FileUtil("SomeFile.txt"); 8: m_file.Create("CONTENT"); 9: } 10: 11: public void Dispose() 12: { 13: m_file.Delete(); 14: } 15: 16: [Fact] 17: public void TestReadOK() 18: { 19: Assert.DoesNotThrow(() => { m_file.Read(); }); 20: } 21: 22: [Fact] 23: public void TestReadFails() 24: { 25: m_file.Readable = false; 26: Assert.Throws<AccessViolationException>(() => { m_file.Read(); }); 27: } 28: }
With some redundant code broken out into the common setup method (the default constructor if you're not familiar with Xunit.net) there is another thing that bothers me about the TestReadOK-test. We're not actually verifying the content of the file. Let's fix that in the next version of this test.