This article focuses on some tips for the visual Studio (2012+) Unit Testing Framework:
- How to emulate a static constructor for a class
- How to test if a method has been called
- How to test the number of times a method executes
- Test considerations for Parallel programming
first, how to simulate the static constructor of a class1.1 code under test
namespaceblogdemo.utdemo.tricks{ Public classStaticconstructorexample {Staticstaticconstructorexample () {//step1:read data from Web service//step2:save data into database } Public intdosomething () {return 0; } }}
The above class has a static constructor that does two things:
- Reading data from a WebService
- Saves the data read to the database.
Both of these things are a heavy reliance on the outside world.
We need to test the method is dosomething, need to test this class, we need to instantiate staticconstructorexample this class, before instantiating this class, its static constructor will be automatically executed: this point is the problem.
- Static constructors depend on the external environment, and this external environment is likely to hang up one day
- If the external environment hangs, the static constructor will error, and our unit test will not run as expected
- The dosomething itself is not directly dependent on the external environment, but indirectly relies on the external environment through a static constructor, which violates the unit test repeatable principle (when the external environment is hung, our tests will not run)
The solution is still shim, and the static constructor of a class can be simulated by Ms.test's shim, thereby altering the behavior of the static constructor and eliminating external dependencies.
1.2 test Code
[TestClass] Public classstaticconstructorexampletests {[TestMethod] Public voiddosomethingtest () {using(Shimscontext.create ()) {varisstaticconstructorexecuted =false; Shimstaticconstructorexample. StaticConstructor = () + = { //Step1:mock Web Service//Step2:mock Databaseisstaticconstructorexecuted =true; }; varExample =Newstaticconstructorexample (); vardata =example. DoSomething (); Assert.AreEqual (0, data); Assert.istrue (isstaticconstructorexecuted); } } }
The above code is implemented by Shimclassname.staticconstructor to simulate a static constructor.
second, how to test a method has been called2.1 Code under test
Base code, calling in Democlass
Public InterfaceIgoodactionhandler {voidAction (); } Public InterfaceIbadactionhandler {voidAction (); } Public classGoodhandler:igoodactionhandler { Public voidAction () {Throw Newnotimplementedexception (); } } Public classBadhandler:ibadactionhandler { Public voidAction () {Throw Newnotimplementedexception (); } }
The class to be tested:
Public classDemoClass {Igoodactionhandler goodhandler; Ibadactionhandler Badhandler; PublicDemoClass () { This. Goodhandler =NewGoodhandler (); This. Badhandler =NewBadhandler (); } Public voidDoSomething (inttype) { if(Type = =1) {Dosomethinggood (); } Else{Dosomethingbad (); } } Private voidDosomethinggood () { This. Goodhandler.action (); } Private voidDosomethingbad () { This. Badhandler.action (); } }
To test the DoSomething method above, both Dosomethinggood and Dosomethingbad rely on the self-interface, which can be used to mock the test. The DoSomething method on the side-test is only necessary to verify that the action of Badhandler is executed if type is 1 o'clock executed goodhandler action.
This is also a key point of unit testing: the Focus Unit . There is no concern for the internal logic of Goodhandler and Badhandler (the two handler internal logic can be tested separately and belong to another unit ), where only the logical routing to the correct handler is followed.
2.2 Test Code
[TestClass] Public classdemoclasstests {[TestMethod] Public voiddosomething_dosomethinggood_tests () {vargoodhandlerexecuted =false; Stubigoodactionhandler Goodhandler=NewStubigoodactionhandler () {Action= () + ={goodhandlerexecuted=true;//true if Goodhandler is executed } }; varbadhandlerexecuted =false; Stubibadactionhandler Badhandler=NewStubibadactionhandler () {Action= () + ={badhandlerexecuted=true;//true if Badhandler is executed } }; vardemoclass=NewDemoClass (); Democlass.badhandler=badhandler;//Inject BadhandlerDemoclass.goodhandler=goodhandler;//Inject GoodhandlerDemoclass.dosomething (1); Assert.istrue (goodhandlerexecuted);//executed Goodhandler assert.isfalse (badhandlerexecuted);//did not execute Badhandler } }
There are two techniques used
- Interface-oriented programming, using stub technology, dynamically injecting stubs (piling), simulating objects at test time
- Changes the behavior of the stub object, this article simply indicates whether this step was performed by setting the value of the flag bit inside the stub object.
The above flag bit method shim technique is still used, the static constructor simulation is to verify that the constructor is executed by modifying the identity bit inside the constructor after shim. The number of test executions that are introduced next is also achieved by modifying the identity bit after shim.
third, how to test the number of times a method executes3.1 code under test
Public classDemoClass {PrivateLoophandler Handler; PublicDemoClass () {handler=NewLoophandler (); } Public voidDoSomething (intTimes ) { for(vari =0; I < times; i++) {handler. Do (); } } } Public classLoophandler { Public voidDo () {}}
Now verify that DoSomething has performed the Times do method.
3.2 Test Code
[TestMethod] Public voiddosomething_runtimes_test () {using(Shimscontext.create ()) {varTimes =0; ShimLoopHandler.AllInstances.Do= (@this) = ={ times++;//Each entry is added once }; varGiventimes = -; NewDemoClass (). DoSomething (Giventimes); Assert.AreEqual (Times, giventimes); } }
The thought is the same as the above stub, and the counter is added inside the shim simulation method.
3.3 Test considerations for Parallel programming
One thing to note here is that because the INT + + is not thread-safe, the test code needs to be adjusted if the Democlass loop is modified to be executed in parallel by multiple threads. , lock the times++.
Public voidDoSomething (intTimes ) {Parallel.For (0, Times, (item) =//Parallel Execution{handler. Do (); }); } [TestMethod] Public voiddosomething_runtimes_test () {using(Shimscontext.create ()) {varTimes =0; ShimLoopHandler.AllInstances.Do= (@this) = = { Lock( This)//Lock Thread Safety{ times++;//Each entry is added once } }; varGiventimes = -; NewDemoClass (). DoSomething (Giventimes); Assert.AreEqual (Times, giventimes); } }
The above code when times are not particularly large when the general does not add lock is also possible, not particularly large when the general does not produce thread safety issues. But when the times are bigger, they can go wrong without lock.
Remove lock when times are 100000. I ran into a problem:
Testing is to be rigorous, if there is a parallel problem, the test is also a good time to add lock.
Unit Test Via Visual studio-part5