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: namespace Advent24 2: { 3: public class FileUtil_Tests 4: { 5: public FileUtilWithDelete Given_A_Readable_File(string content) 6: { 7: FileUtilWithDelete file = new FileUtilWithDelete("SomeFile.txt"); 8: file.Create(content); 9: return file; 10: } 11: } 12: 13: public class FileUtil_Tests_With_Readable_File : FileUtil_Tests 14: { 15: [Fact] 16: public void Reading_A_Readable_File_Returns_File_Content() 17: { 18: using (FileUtilWithDelete file = Given_A_Readable_File("CONTENT")) 19: { 20: string content = file.Read(); 21: Assert.Equal<string>("CONTENT", content); 22: } 23: } 24: } 25: 26: public class FileUtil_Tests_With_Unreadable_File : FileUtil_Tests 27: { 28: private FileUtilWithDelete Given_An_Unreadable_File() 29: { 30: FileUtilWithDelete file = Given_A_Readable_File("SHOULD NOT BE ABLE TO READ THIS"); 31: file.Readable = false; 32: return file; 33: } 34: 35: [Fact] 36: public void Reading_An_Unreadable_File_Throws_Correct_Exception() 37: { 38: using (FileUtilWithDelete file = Given_An_Unreadable_File()) 39: { 40: Assert.Throws<AccessViolationException>(() => { file.Read(); }); 41: } 42: } 43: } 44: }
This is the final version of this test. Tomorrow you'll get my final thoughts on this advent calendar.
1: namespace Advent23 2: { 3: public class FileUtil_Tests_With_Readable_File 4: { 5: private FileUtilWithDelete Given_A_Readable_File(string content) 6: { 7: FileUtilWithDelete file = new FileUtilWithDelete("SomeFile.txt"); 8: file.Create(content); 9: return file; 10: } 11: 12: [Fact] 13: public void Reading_A_Readable_File_Returns_File_Content() 14: { 15: using (FileUtilWithDelete file = Given_A_Readable_File("CONTENT")) 16: { 17: string content = file.Read(); 18: Assert.Equal<string>("CONTENT", content); 19: } 20: } 21: } 22: 23: public class FileUtil_Tests_With_Unreadable_File 24: { 25: private FileUtilWithDelete Given_An_Unreadable_File() 26: { 27: FileUtilWithDelete file = new FileUtilWithDelete("SomeFile.txt"); 28: file.Create("SHOULD NOT BE ABLE TO READ THIS"); 29: file.Readable = false; 30: return file; 31: } 32: 33: [Fact] 34: public void Reading_An_Unreadable_File_Throws_Correct_Exception() 35: { 36: using (FileUtilWithDelete file = Given_An_Unreadable_File()) 37: { 38: Assert.Throws<AccessViolationException>(() => { file.Read(); }); 39: } 40: } 41: } 42: }
Once again we've ended up with the same code in different places. That should be fixed.
1: namespace Advent22 2: { 3: public class FileUtil_Specification 4: { 5: private FileUtilWithDelete Given_A_Readable_File(string content) 6: { 7: FileUtilWithDelete file = new FileUtilWithDelete("SomeFile.txt"); 8: file.Create(content); 9: return file; 10: } 11: 12: private FileUtilWithDelete Given_An_Unreadable_File() 13: { 14: FileUtilWithDelete file = Given_A_Readable_File("SHOULD NOT BE ABLE TO READ THIS"); 15: file.Readable = false; 16: return file; 17: } 18: 19: [Fact] 20: public void Reading_A_Readable_File_Returns_File_Content() 21: { 22: using (FileUtilWithDelete file = Given_A_Readable_File("CONTENT")) 23: { 24: string content = file.Read(); 25: Assert.Equal<string>("CONTENT", content); 26: } 27: } 28: 29: [Fact] 30: public void Reading_An_Unreadable_File_Throws_Correct_Exception() 31: { 32: using (FileUtilWithDelete file = Given_An_Unreadable_File()) 33: { 34: Assert.Throws<AccessViolationException>(() => { file.Read(); }); 35: } 36: } 37: } 38: }
So now we have a better name for our specification. But why should everything that has to do with FileUtil be in the same specification?
1: public class Advent21 2: { 3: private FileUtilWithDelete Given_A_Readable_File(string content) 4: { 5: FileUtilWithDelete file = new FileUtilWithDelete("SomeFile.txt"); 6: file.Create(content); 7: return file; 8: } 9: 10: private FileUtilWithDelete Given_An_Unreadable_File() 11: { 12: FileUtilWithDelete file = Given_A_Readable_File("SHOULD NOT BE ABLE TO READ THIS"); 13: file.Readable = false; 14: return file; 15: } 16: 17: [Fact] 18: public void Reading_A_Readable_File_Returns_File_Content() 19: { 20: using (FileUtilWithDelete file = Given_A_Readable_File("CONTENT")) 21: { 22: string content = file.Read(); 23: Assert.Equal<string>("CONTENT", content); 24: } 25: } 26: 27: [Fact] 28: public void Reading_An_Unreadable_File_Throws_Correct_Exception() 29: { 30: using (FileUtilWithDelete file = Given_An_Unreadable_File()) 31: { 32: Assert.Throws<AccessViolationException>(() => { file.Read(); }); 33: } 34: } 35: }
Now let's consider the name of the class containing all tests. A pretty bad name if you ask me. And since we've used the "given" pattern from BDD we can use BDD to inspire us into a better "test class name".
1: public class Advent20 2: { 3: private FileUtilWithDelete Given_A_File(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 Reading_A_Readable_File_Returns_File_Content() 13: { 14: using (FileUtilWithDelete file = Given_A_File("CONTENT", true)) 15: { 16: string content = file.Read(); 17: Assert.Equal<string>("CONTENT", content); 18: } 19: } 20: 21: [Fact] 22: public void Reading_An_Unreadable_File_Throws_Correct_Exception() 23: { 24: using (FileUtilWithDelete file = Given_A_File("SHOULD NOT BE ABLE TO READ THIS", false)) 25: { 26: Assert.Throws<AccessViolationException>(() => { file.Read(); }); 27: } 28: } 29: }
The "Given_By" pattern has been "stolen" from BDD and the whole purpose is to make the test as readable as possible. Reading a test should more or less be like reading a paragraph. So "Given_A_File" is not really that great. It is probably better to have different Given-methods depending on what we want.
1: public class Advent19 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 Reading_A_Readable_File_Returns_File_Content() 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 Reading_An_Unreadable_File_Throws_Correct_Exception() 23: { 24: using (FileUtilWithDelete file = SetUp("SHOULD NOT BE ABLE TO READ THIS", false)) 25: { 26: Assert.Throws<AccessViolationException>(() => { file.Read(); }); 27: } 28: } 29: }
With such good test names, the name of the setup-method embarrasses me. Let's change it!
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 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 Advent16 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 TestReadReadableFile() 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 TestReadUnreadableFile() 23: { 24: using (FileUtilWithDelete file = SetUp("SHOULD NOT BE ABLE TO READ THIS", false)) 25: { 26: Assert.Throws<AccessViolationException>(() => { file.Read(); }); 27: } 28: } 29: }
This test code starts to look pretty decent, don't you think? Well I think it is time to address the naming of the tests again. Even though I know the circumstances I'm testing I think the name should include something about the expected result also. And "Failing" that we used before is not really a good expected result (but it is better than nothing which is the case now).