Test-Driven Development In .NET Summary

xiaoxiao2021-03-06  96

Test-Driven Development in .nethttp://www.codeproject.com/dotnet/tdd_in_dotnet.asp

Summary:

Using Mock Objects - DotNetMock

One of the biggest challenges you will face when writing units tests is to make sure that each test is only testing one thing. It is very common to have a situation where the method you are testing uses other objects to do its work. If you write a test for this method you end up testing not only the code in that method, but also code in the other classes. This is a problem. Instead we use mock objects to ensure that we are only testing the code we intend to test. A Mock Object Emulates A Real class and helps Test Expections About How That Class Is Used. Most Importantly, Mock Objects Are:

Easy to make easy to set up fast deterministic (product predictable results) Allow Easy Verification The Correct Calls WERE MADE, Perhaps in The Right ORDER

..........................

Namespace UnitTestingexamples.tests

{

Using DotNetMock;

Using system;

[TestFixTure]

Public Class Modeltests

{

[TEST]

Public void testsave ()

{

MockDatabase DB = New MockDatabase ();

db.sexpectedupdates (2);

Modelclass model = new modelclass ();

Model.save (dB);

Db.verify ();

}

}

}

As you can see, the MockDatabase was easy to setup and allowed us to confirm that the Save method made certain calls on it. Also notice that the mock object prevents us from having to worry about real databases. We know that when the ModelClass saves itself , it should call the database's Update method twice. So we tell the MockDatabase to expect two updates calls, call Save and the confirm that what we expected really happened. Because the MockDatabase does not really connect to a database, we do not have . to worry about keeping "test data" around We only test that the Save code causes two updates "When Mock Objects are used, only the unit test and the target domain code are real." - Endo-Testing:. Unit Testing with Mock Objects by Tim Mackinnon, Steve FreeMan and Philip Craig.

Testing The Business Layer

Testing the business layer is where most developers feel comfortable talking about unit testing. Well designed business layer classes are loosely coupled and highly cohesive. In practical terms, coupling described the level of dependence that classes have with one another. If a class is loosely coupled , then making a change to one class should not have an impact on another class. On the other hand, a highly cohesive class is a class that does the one thing is was designed to do and nothing else. If you create your business layer class library so You should be able to create a single class for each business class. You should be able to test each of its public methods with a minimal amount that the classes are loosely coupled and highly cohesive, then creating useful unit tests is easy. test effect. by the way Riting Your Tests First, You Shouldn't Have this Problem.testing The User Interface

When you start to write the user interface for your application, a number of different problems arise. Although you can create user interface classes that are loosely coupled with respect to other classes, a user interface class is by definition highly coupled to the user! So how can we create a automated unit test to test this The answer is that we separate the logic of our user interface from the actual presentation of the view Various patterns exist in the literature under a variety of different names:?. Model-View-Controller , Model-View-Presenter, Doc-View, etc. The creators of these patterns recognized that decoupling the logic of what the view does (ie, controller) from the view is a Good Thing. So how do we use this? The technique I use comes from Michael Feathers' paper The Humble Dialog Box. The idea is to make the view class support a simple interface used for getting and setting the values ​​displayed by the view. There is basically no code in the view except for code relate d to the painting of the screen. The event handlers in the view for each interactive user interface element (eg, a button) contain nothing more than a pass-thru to a method in the controller. The best way to illustrate this concept is with an example. Assume our application needs a screen that asks the user for their name and social security number. Both fields are required, so we need to make sure that a name is entered and the SSN has the correct format. Since we are writing our Unit Tests First, We Write The Following Test: [TestFixTure]

Public Class VitalSControllertests

{

[TEST]

Public void testsuccessful ()

{

MockVitalsView View = New MockVitalsView ();

VitalScontroller Controller = New VitalSController (View);

View.name = "peter provost"; view.ssn = "123-45-6789";

Assertion.assert (Controller.Onok () == true);

}

[TEST]

Public void testfailed ()

{

MockVitalsView View = New MockVitalsView ();

VitalScontroller Controller = New VitalSController (View);

View.name = "";

View.ssn = "123-45-6789";

View.sexpectedErrorMessage (Controller.Error_Message_bad_name);

Assertion.Assert (Controller.Onok () == false);

View.verify ();

View.name = "peter provost";

View.ssn = "";

View.sexpectedErrorMessage (Controller.Error_Message_bad_ssn);

Assertion.Assert (Controller.Onok () == false);

View.verify ()

}

}

When we build this we receive a lot of compiler errors because we do not have either a MockVitalsView or a VitalsController. So let's write skeletons of those classes. Remember, we only want to write enough to make this code compile.

Public Class MockVitalsView

{

Public String Name

{

Get {return null;}

Set {}

}

Public String SSN

{

Get {return null;}

Set {}

}

Public void setExpectedErrorMessage (String Message)

{

}

Public void verify ()

{

Throw new notimplementException ();

}

}

Public Class VitalScontroller

{

Public const string error_message_bad_ssn = "BAD SSN.";

Public const string error_message_bad_name = "Bad name.";

Public VitalScontroller (MockVitalSView View)

{

}

Public bool onok ()

{

Return False;

}

}

Now our test assembly compiles and when we run the tests, the test runner reports two failures. The first occurs when TestSuccessful calls controller.OnOk, because the result is false rather than the expected true value. The second failure occurs when TestFailed calls view. . Verify Continuing on with our test-first paradigm, we now need to make these tests pass It is relatively simple to make TestSuccessful pass, but to make TestFailed pass, we actually have to write some real code, such as:. public class MockVitalsView : MOCKOBJECT

{

Public String Name

{

Get {return _name;}

Set {_name = value;}

}

Public String SSN

{

Get {return _ssn;}

Set {_ssn = value;}

}

Public String ErrorMessage

{

Get {return _expectedErrorMessage.Actual;

Set {_expectedErrorMessage.Actual = value;}

}

Public void setExpectedErrorMessage (String Message)

{

_expectederrorMessage.expected = message;

}

Private string _name;

Private string _ssn;

Private expertationstring _expectederrorMessage =

New Expectationstring ("Expected Error Message");

}

Public Class VitalScontroller

{

Public const string error_message_bad_ssn = "BAD SSN.";

Public const string error_message_bad_name = "Bad name.";

Public VitalScontroller (MockVitalSView View)

{

_VIEW = view;

}

Public bool onok ()

{

IF (isvalidname () == false)

{

_View.errorMessage = error_message_bad_name;

Return False;

}

IF (isvalidssn () == false)

{

_View.ErrorMessage = error_message_bad_ssn;

Return False;

}

// All is Well, Do Something ...

Return True;

}

Private bool isvalidname ()

{

Return_View.name.length> 0;

}

Private bool isvalidssn ()

{

String pattern = @ "^ / d {3} - / d {2} - / d {4} $"; return regex.ismatch (_View.ssn, pattern);

}

Private mockvitalsview _View;

}

Let's briefly review this code before proceeding. The first thing to notice is that we have not changed the tests at all (which is why I did not even bother to show them). We did, however, make significant changes to both MockVitalsView and VitalsController. Let's begin with the MockVitalsView. In our previous example, MockVitalsView did not derive from any base class. to make our lives easier, we changed it to derive from DotNetMock.MockObject. The MockObject class gives us a stock implementation of Verify that does all the work for us. It does this by using expectation classes through which we indicate what we expect to happen to our mock object. In this case our tests are expecting specific values ​​for the ErrorMessage property. This property is a string, so we add an ExpectationString member to our mock object. Then we implement the SetExpectedErrorMessage method and the ErrorMessage property to use this object. When we call Verify in our test code, the MockObject base class will check this expectation and identify anything that does not happen as expected. Pretty cool, eh? The other class that changed was our VitalsController class. Because this is where all the working code resides, we expected there to be quite a few changes here. Basically , we implemented the core logic of the view in the OnOk method. We use the accessor methods defined in the view to read the input values, and if an error occurs, we use the ErrorMessage property to write out an appropriate message. So we ' RE DONE, RIGHT? NOT QUITE. AT THIS A WORKING TEST OF A Controller Using A Mock View. We don '

t have anything to show the customer! What we need to do is use this controller with a real implementation of a view. How do we do that? The first thing we need to do is extract an interface from MockVitalsView. A quick look at VitalsController And VitalScontrollertests Shows US That The Following Interface Will Work.public Interface IvitalsView

{

String name {get; set;}

String ssn {get; set;}

String ErrorMessage {Get; set;}

}

After creating the new interface, we change all references to MockVitalsView to IVitalsView in the controller and we add IVitalsView to the inheritance chain of MockVitalsView. And, of course, after performing this refactoring job we run our tests again. Assuming everything is fine, we Create Our New View. for this Example I Will Be Creating An ASP.NET Page to Act As The View, But You Could Just As Easily Create A Windows Form. Here is The .aspx file:

<% @ Page language = "c #" codebehind = "vitalsview.aspx.cs"

Autoeventwireup = "false"

Inherits = "UnitTestingExamples.VitalsView"%>

VitalsView </ Title></p> <p><meta name = "generator" content = "Microsoft Visual Studio 7.0"></p> <p><meta name = "code_language" content = "c #"></p> <p><meta name = vs_defaultclientscript content = "javascript"></p> <p><meta name = vs_targetschema content = "http://schemas.microsoft.com/intellisense/ie5"></p> <p></ hEAD></p> <p><body ms_positioning = "gridLayout"></p> <p><form id = "vitalsview" Method = "post" runat = "server"></p> <p><Table Border = "0"></p> <p><tr></p> <p><TD> Name: </ TD></p> <p><TD> <asp: textbox runat = server id = nametextbox /> </ td></p> <p></ TR></p> <p><tr></p> <p><TD> SSN: </ TD></p> <p><TD> <asp: textbox runat = server id = ssntextbox /> </ td></p> <p></ TR></p> <p><tr></p> <p><TD> </ td></p> <p><TD> <asp: label runat = server id = errorMessageLabel /> </ td></p> <p></ TR></p> <p><tr></p> <p><TD> </ td></p> <p><TD> <asp: button runat = server id = OKButton text = "ok" /> </ td></p> <p></ TR></p> <p></ table></p> <p></ form></p> <p></ body></p> <p></ html></p> <p>And Here Is The Code-Behind File:</p> <p>Using system;</p> <p>Using system.Web.ui.webcontrols;</p> <p>Using UnitTestingExamples.library;</p> <p>Namespace UnitTestingexamples</p> <p>{</p> <p>/// <summary></p> <p>/// summary description for vitalsview.</p> <p>/// </ summary></p> <p>Public class vitalsview: system.Web.ui.page, iVitalsview</p> <p>{</p> <p>Protected textbox nametextbox;</p> <p>Protected textbox ssnTextBox;</p> <p>Protected label errorMessageLabel;</p> <p>Protected button okbutton;</p> <p>Private vitalscontroller _Controller;</p> <p>Private Void Page_Load (Object Sender, System.EventArgs E)</p> <p>{</p> <p>_Controller = New VitalScontroller (this);</p> <p>}</p> <p>Private void Okbutton_Click (Object Sender, System.EventArgs E)</p> <p>{</p> <p>IF (_Controller.Onok () == True)</p> <p>Response.Redirect ("Thankyou.aspx");</p> <p>}</p> <p>#Region IvitalSView Implementation</p> <p>Public String Name</p> <p>{</p> <p>Get {return nametextbox.text;}</p> <p>Set {nametextbox.text = value;</p> <p>}</p> <p>Public String SSN</p> <p>{</p> <p>Get {return ssnTextBox.Text;}</p> <p>Set {ssnTextBox.text = value;</p> <p>}</p> <p>Public String ErrorMessage</p> <p>{</p> <p>Get {return errorMessageLabel.text;}</p> <p>Set {ErrorMessageLabel.Text = Value;</p> <p>}</p> <p>#ndregion</p> <p>#Region Web Form Designer Generated CodeOverride Protected Void OnInit (Eventargs E)</p> <p>{</p> <p>//</p> <p>// Codegen: This Call is Required by The ASP.NET Web Form Designer.</p> <p>//</p> <p>InitializationComponent ();</p> <p>Base.onit (E);</p> <p>}</p> <p>/// <summary></p> <p>/// Required Method for Designer Support - Do Not Modify</p> <p>/// The contents of this method with the code editor.</p> <p>/// </ summary></p> <p>Private vidinitiRizeComponent ()</p> <p>{</p> <p>This.Load = New System.EventHandler (this.page_load);</p> <p>Okbutton.click = new system.eventhandler (this.okbutton_click);</p> <p>}</p> <p>#ndregion</p> <p>}</p> <p>}</p> <p>As you can see, the only code in the view is code that ties the IVitalsView interface to the ASP.NET Web Controls and a couple of lines to create the controller and call its methods. Views like this are easy to implement. Also, because ......................</p> <p>Conclusion</p> <p>Test-driven development is a powerful technique that you can use today to improve the quality of your code. It forces you to think carefully about the design of your code, and is ensures that all of your code is tested. Without adequate unit tests, Refactoring EXISTING CODE IS NEXT TO IMPOSSIBLE. VERY FEW PEOPLE WHO Take The PLUNGE INTO TDD LATER DECIDE TOP DOING IT. TRY AND You will see it it is a good thing.</p> <p>More info on the web</p> <p>NUnit</p> <p>http://www.nunit.org/</p> <p>CsUnit</p> <p>http://www.csunit.org/ (an alternative to nunit)</p> <p>DotNetMock</p> <p>http://sourceforge.net/projects/dotnetmock/</p> <p>MockObjects</p> <p>http://www.mockOckObjects.com/</p> <p>The Humble Dialog Box</p> <p>http://www.objectmentor.com/resources/articles/thehumbledialogbox.pdf</p></div><div class="text-center mt-3 text-grey"> 转载请注明原文地址:https://www.9cbs.com/read-125983.html</div><div class="plugin d-flex justify-content-center mt-3"></div><hr><div class="row"><div class="col-lg-12 text-muted mt-2"><i class="icon-tags mr-2"></i><span class="badge border border-secondary mr-2"><h2 class="h6 mb-0 small"><a class="text-secondary" href="tag-2.html">9cbs</a></h2></span></div></div></div></div><div class="card card-postlist border-white shadow"><div class="card-body"><div class="card-title"><div class="d-flex justify-content-between"><div><b>New Post</b>(<span class="posts">0</span>) </div><div></div></div></div><ul class="postlist list-unstyled"> </ul></div></div><div class="d-none threadlist"><input type="checkbox" name="modtid" value="125983" checked /></div></div></div></div></div><footer class="text-muted small bg-dark py-4 mt-3" id="footer"><div class="container"><div class="row"><div class="col">CopyRight © 2020 All Rights Reserved </div><div class="col text-right">Processed: <b>0.050</b>, SQL: <b>9</b></div></div></div></footer><script src="./lang/en-us/lang.js?2.2.0"></script><script src="view/js/jquery.min.js?2.2.0"></script><script src="view/js/popper.min.js?2.2.0"></script><script src="view/js/bootstrap.min.js?2.2.0"></script><script src="view/js/xiuno.js?2.2.0"></script><script src="view/js/bootstrap-plugin.js?2.2.0"></script><script src="view/js/async.min.js?2.2.0"></script><script src="view/js/form.js?2.2.0"></script><script> var debug = DEBUG = 0; var url_rewrite_on = 1; var url_path = './'; var forumarr = {"1":"Tech"}; var fid = 1; var uid = 0; var gid = 0; xn.options.water_image_url = 'view/img/water-small.png'; </script><script src="view/js/wellcms.js?2.2.0"></script><a class="scroll-to-top rounded" href="javascript:void(0);"><i class="icon-angle-up"></i></a><a class="scroll-to-bottom rounded" href="javascript:void(0);" style="display: inline;"><i class="icon-angle-down"></i></a></body></html><script> var forum_url = 'list-1.html'; var safe_token = '7AwujNa_2B_2BLhyp8CZFG7CGGOT2xfBTg6DTQLUBheMdZ5D4IFzOXd_2BLeAenf5jVTAuj6_2BpmWdQGYGJHGQKES6P9A_3D_3D'; var body = $('body'); body.on('submit', '#form', function() { var jthis = $(this); var jsubmit = jthis.find('#submit'); jthis.reset(); jsubmit.button('loading'); var postdata = jthis.serializeObject(); $.xpost(jthis.attr('action'), postdata, function(code, message) { if(code == 0) { location.reload(); } else { $.alert(message); jsubmit.button('reset'); } }); return false; }); function resize_image() { var jmessagelist = $('div.message'); var first_width = jmessagelist.width(); jmessagelist.each(function() { var jdiv = $(this); var maxwidth = jdiv.attr('isfirst') ? first_width : jdiv.width(); var jmessage_width = Math.min(jdiv.width(), maxwidth); jdiv.find('img, embed, iframe, video').each(function() { var jimg = $(this); var img_width = this.org_width; var img_height = this.org_height; if(!img_width) { var img_width = jimg.attr('width'); var img_height = jimg.attr('height'); this.org_width = img_width; this.org_height = img_height; } if(img_width > jmessage_width) { if(this.tagName == 'IMG') { jimg.width(jmessage_width); jimg.css('height', 'auto'); jimg.css('cursor', 'pointer'); jimg.on('click', function() { }); } else { jimg.width(jmessage_width); var height = (img_height / img_width) * jimg.width(); jimg.height(height); } } }); }); } function resize_table() { $('div.message').each(function() { var jdiv = $(this); jdiv.find('table').addClass('table').wrap('<div class="table-responsive"></div>'); }); } $(function() { resize_image(); resize_table(); $(window).on('resize', resize_image); }); var jmessage = $('#message'); jmessage.on('focus', function() {if(jmessage.t) { clearTimeout(jmessage.t); jmessage.t = null; } jmessage.css('height', '6rem'); }); jmessage.on('blur', function() {jmessage.t = setTimeout(function() { jmessage.css('height', '2.5rem');}, 1000); }); $('#nav li[data-active="fid-1"]').addClass('active'); </script>