MSDN Magazine - December 2008 - (Page 39) Now, all of those things are important, and all of them should be tested at some point in time. The key, though, is to decouple as much of your core application as possible away from this infrastructure to at least make your core application code easy to test. I should be able to test my data access in isolation once, then write simple tests for the business logic without any concern for the database. Back to my sample workflow subsystem. At the time I was struggling to get the business logic correct, but the XML data setup and data access was slowing me down. What I really needed to do was walk right up to the business logic code and work with it entirely in memory with quick-running tests. The first thing I should have done was to isolate the business logic away from the infrastructure and XML: public class WorkflowState { public DateTime LastChanged { get; set; } public string CurrentStatus { get; set; } public string Priority { get; set; } // Other properties public void ChangeStatus(string newStatus) { // WorkflowState will update itself based on // the current state of the WorkflowState object // and the newStatus } Figure 2 Processing Workflow Events public class ChangeWorkflowStatusMessage { public string NewStatus { get; set; } public long WorkflowId { get; set; } } public class WorkflowService { private readonly IEmailGateway _emailGateway; private readonly IRepository _repository; public WorkflowService(IEmailGateway emailGateway, IRepository repository) { _emailGateway = emailGateway; _repository = repository; } public void ChangeStatus(ChangeWorkflowStatusMessage message) { // Fetch the correct WorkflowState object from the database WorkflowState workflow = _repository.Find (message.WorkflowId); workflow.ChangeStatus(message.NewStatus); // email the owner of this workflow and tell them that something happened // if this is an Urgent item if (workflow.Priority == "Urgent") { // workflow.Owner is the user who is responsible for // this workflow _emailGateway.SendChangeStatusEmailTo(workflow, workflow.Owner); } // Save any changes to the WorkflowState object _repository.Save(workflow); } } } Let’s say that you have a business rule that says you should escalate the priority of the WorkflowState to Urgent if the customer is upset. Now the workflow business logic can be tested in isolation from the database infrastructure and the XML like this: [TestFixture] public class WorkflowStateTester { [Test] public void the_priority_should_be_escalated_to_urgent_when_the_customer_is_angry() { WorkflowState state = new WorkflowState(){Priority = "Low", CurrentStatus = "NotStarted"}; state.ChangeStatus("CustomerIsVeryAngry"); } state.Priority.ShouldEqual("Urgent!"); } The workflow business logic is easier to test, but what else did SpecUnit In many of the unit test samples, i’m using the extension methods from the SpecUnit library (see code.google.com/p/ specunit-net). Among other things, SpecUnit provides a series of extension methods that help make unit tests much more readable than the classic xUnit Assert.Areequal(expected, actual) syntax. Here’s a sample from SpecUnit: public static object ShouldEqual( this object actual, object expected) { Assert.AreEqual(expected, actual); return expected; } you get out of this change? You’ve made the business logic easier to write and understand because it’s now independent from the data access infrastructure and the XML format. You now have a much greater ability to change the data access and the business logic independently. Testability is extremely important in areas of the code that have a lot of variability, require many permutations of input to adequately test, or are volatile in regard to changing requirements. In the original ProcessWorkflowEvent method shown in Figure 1, the act of loading and persisting the XML document to and from the database is basically the same no matter what is happening with the workflow business logic. I can write a small handful of tests against that persistence code and be confident that it works. It took many more test scenarios to adequately test all the code paths through the business logic. I could have saved myself quite a bit of effort on the original project if I’d been able to unit test the business logic in complete isolation. Using Fakes to Establish Boundary Conditions And this method would be used like this within unit tests: item.Name.ShouldEqual("Smith"); item.Age.ShouldEqual(9); SpecUnit is usable from any of the popular xUnit tools for .NeT. my team is using SpecUnit plus many extension methods that are specific to our application to make unit tests a little bit easier to write and much easier to read. msdnmagazine.com You might have noticed that the second version of the workflow logic isn’t complete. It still needs to interact with the database and possibly an e-mail server as well. I would rewrite the original ProcessWorkflowEvent method to something like the method on a new WorkflowService class shown in Figure 2. Instead of doing the data access and sending e-mail directly in WorkflowService, I’ll have it delegate to some new services named IRepository and IEmailGateway that I’ll explain very soon. One of the prerequisites for automated testing is establishing known inputs to the test. To fully test the WorkflowService.ChangeStatus functionality, I need to set up test inputs that would work on WorkflowState objects that are and are not Urgent. I could set December 2008 39 http://code.google.com/p/specunit-net http://code.google.com/p/specunit-net http://www.msdnmagazine.com
Table of Contents Feed for the Digital Edition of MSDN Magazine - December 2008 MSDN Magazine - December 2008 Contents Toolbox CLR Inside Out Advanced Basics Cutting Edge Patterns In Practice Team System Real-World WF Visual Studio OBA Tools SOA Data Access Geneva Framework Test Run Foundations Windows With C++ Going Places End Bracket MSDN Magazine - December 2008 MSDN Magazine - December 2008 - (Page Intro) MSDN Magazine - December 2008 - Contents (Page Cover1) MSDN Magazine - December 2008 - Contents (Page Cover2) MSDN Magazine - December 2008 - Contents (Page 1) MSDN Magazine - December 2008 - Contents (Page 2) MSDN Magazine - December 2008 - Contents (Page 3) MSDN Magazine - December 2008 - Contents (Page 4) MSDN Magazine - December 2008 - Contents (Page 5) MSDN Magazine - December 2008 - Contents (Page 6) MSDN Magazine - December 2008 - Contents (Page 7) MSDN Magazine - December 2008 - Contents (Page 8) MSDN Magazine - December 2008 - Contents (Page 9) MSDN Magazine - December 2008 - Contents (Page 10) MSDN Magazine - December 2008 - Toolbox (Page 11) MSDN Magazine - December 2008 - Toolbox (Page 12) MSDN Magazine - December 2008 - Toolbox (Page 13) MSDN Magazine - December 2008 - Toolbox (Page 14) MSDN Magazine - December 2008 - CLR Inside Out (Page 15) MSDN Magazine - December 2008 - CLR Inside Out (Page 16) MSDN Magazine - December 2008 - CLR Inside Out (Page 17) MSDN Magazine - December 2008 - CLR Inside Out (Page 18) MSDN Magazine - December 2008 - CLR Inside Out (Page 19) MSDN Magazine - December 2008 - CLR Inside Out (Page 20) MSDN Magazine - December 2008 - CLR Inside Out (Page 21) MSDN Magazine - December 2008 - Advanced Basics (Page 22) MSDN Magazine - December 2008 - Advanced Basics (Page 23) MSDN Magazine - December 2008 - Advanced Basics (Page 24) MSDN Magazine - December 2008 - Advanced Basics (Page 25) MSDN Magazine - December 2008 - Advanced Basics (Page 26) MSDN Magazine - December 2008 - Advanced Basics (Page 27) MSDN Magazine - December 2008 - Advanced Basics (Page 28) MSDN Magazine - December 2008 - Cutting Edge (Page 29) MSDN Magazine - December 2008 - Cutting Edge (Page 30) MSDN Magazine - December 2008 - Cutting Edge (Page 31) MSDN Magazine - December 2008 - Cutting Edge (Page 32) MSDN Magazine - December 2008 - Cutting Edge (Page 33) MSDN Magazine - December 2008 - Cutting Edge (Page 34) MSDN Magazine - December 2008 - Cutting Edge (Page 35) MSDN Magazine - December 2008 - Cutting Edge (Page 36) MSDN Magazine - December 2008 - Patterns In Practice (Page 37) MSDN Magazine - December 2008 - Patterns In Practice (Page 38) MSDN Magazine - December 2008 - Patterns In Practice (Page 39) MSDN Magazine - December 2008 - Patterns In Practice (Page 40) MSDN Magazine - December 2008 - Patterns In Practice (Page 41) MSDN Magazine - December 2008 - Patterns In Practice (Page 42) MSDN Magazine - December 2008 - Patterns In Practice (Page 43) MSDN Magazine - December 2008 - Team System (Page 44) MSDN Magazine - December 2008 - Team System (Page 45) MSDN Magazine - December 2008 - Team System (Page 46) MSDN Magazine - December 2008 - Team System (Page 47) MSDN Magazine - December 2008 - Team System (Page 48) MSDN Magazine - December 2008 - Team System (Page 49) MSDN Magazine - December 2008 - Team System (Page 50) MSDN Magazine - December 2008 - Team System (Page 51) MSDN Magazine - December 2008 - Real-World WF (Page 52) MSDN Magazine - December 2008 - Real-World WF (Page 53) MSDN Magazine - December 2008 - Real-World WF (Page 54) MSDN Magazine - December 2008 - Real-World WF (Page 55) MSDN Magazine - December 2008 - Real-World WF (Page 56) MSDN Magazine - December 2008 - Real-World WF (Page 57) MSDN Magazine - December 2008 - Real-World WF (Page 58) MSDN Magazine - December 2008 - Real-World WF (Page 59) MSDN Magazine - December 2008 - Real-World WF (Page 60) MSDN Magazine - December 2008 - Real-World WF (Page 61) MSDN Magazine - December 2008 - Visual Studio OBA Tools (Page 62) MSDN Magazine - December 2008 - Visual Studio OBA Tools (Page 63) MSDN Magazine - December 2008 - Visual Studio OBA Tools (Page 64) MSDN Magazine - December 2008 - Visual Studio OBA Tools (Page 65) MSDN Magazine - December 2008 - Visual Studio OBA Tools (Page 66) MSDN Magazine - December 2008 - Visual Studio OBA Tools (Page 67) MSDN Magazine - December 2008 - Visual Studio OBA Tools (Page 68) MSDN Magazine - December 2008 - Visual Studio OBA Tools (Page 69) MSDN Magazine - December 2008 - Visual Studio OBA Tools (Page 70) MSDN Magazine - December 2008 - Visual Studio OBA Tools (Page 71) MSDN Magazine - December 2008 - SOA Data Access (Page 72) MSDN Magazine - December 2008 - SOA Data Access (Page 73) MSDN Magazine - December 2008 - SOA Data Access (Page 74) MSDN Magazine - December 2008 - SOA Data Access (Page 75) MSDN Magazine - December 2008 - SOA Data Access (Page 76) MSDN Magazine - December 2008 - SOA Data Access (Page 77) MSDN Magazine - December 2008 - SOA Data Access (Page 78) MSDN Magazine - December 2008 - SOA Data Access (Page 79) MSDN Magazine - December 2008 - SOA Data Access (Page 80) MSDN Magazine - December 2008 - SOA Data Access (Page 81) MSDN Magazine - December 2008 - Geneva Framework (Page 82) MSDN Magazine - December 2008 - Geneva Framework (Page 83) MSDN Magazine - December 2008 - Geneva Framework (Page 84) MSDN Magazine - December 2008 - Geneva Framework (Page 85) MSDN Magazine - December 2008 - Geneva Framework (Page 86) MSDN Magazine - December 2008 - Geneva Framework (Page 87) MSDN Magazine - December 2008 - Geneva Framework (Page 88) MSDN Magazine - December 2008 - Geneva Framework (Page 89) MSDN Magazine - December 2008 - Geneva Framework (Page 90) MSDN Magazine - December 2008 - Test Run (Page 91) MSDN Magazine - December 2008 - Test Run (Page 92) MSDN Magazine - December 2008 - Test Run (Page 93) MSDN Magazine - December 2008 - Test Run (Page 94) MSDN Magazine - December 2008 - Test Run (Page 95) MSDN Magazine - December 2008 - Test Run (Page 96) MSDN Magazine - December 2008 - Test Run (Page 97) MSDN Magazine - December 2008 - Test Run (Page 98) MSDN Magazine - December 2008 - Test Run (Page 99) MSDN Magazine - December 2008 - Test Run (Page 100) MSDN Magazine - December 2008 - Foundations (Page 101) MSDN Magazine - December 2008 - Foundations (Page 102) MSDN Magazine - December 2008 - Foundations (Page 103) MSDN Magazine - December 2008 - Foundations (Page 104) MSDN Magazine - December 2008 - Foundations (Page 105) MSDN Magazine - December 2008 - Foundations (Page 106) MSDN Magazine - December 2008 - Foundations (Page 107) MSDN Magazine - December 2008 - Foundations (Page 108) MSDN Magazine - December 2008 - Windows With C++ (Page 109) MSDN Magazine - December 2008 - Windows With C++ (Page 110) MSDN Magazine - December 2008 - Windows With C++ (Page 111) MSDN Magazine - December 2008 - Windows With C++ (Page 112) MSDN Magazine - December 2008 - Going Places (Page 113) MSDN Magazine - December 2008 - Going Places (Page 114) MSDN Magazine - December 2008 - Going Places (Page 115) MSDN Magazine - December 2008 - Going Places (Page 116) MSDN Magazine - December 2008 - Going Places (Page 117) MSDN Magazine - December 2008 - Going Places (Page 118) MSDN Magazine - December 2008 - Going Places (Page 119) MSDN Magazine - December 2008 - End Bracket (Page 120) MSDN Magazine - December 2008 - End Bracket (Page Cover3) MSDN Magazine - December 2008 - End Bracket (Page Cover4)
For optimal viewing of this digital publication, please enable JavaScript and then refresh the page. If you would like to try to load the digital publication without using Flash Player detection, please click here.