Document directory
- Problem
- Search
- Implementation ideas and technical details
- Implementation steps
- Notes
- Last
For better typographical skills, I suggest you read the following in my blog:
Http://www.dozer.cc/2012/11/how-to-mock-non-virtual-method-and-sealed-class/
Problem
A common problem is that there are no interfaces.MockNon-Virtual Methods andSealing class?
In my previous article (Sense of Unit Testing) Introduced the unit test principles and some tips, but the code was previously written, there will always be a lot of insurmountable points, and it is impossible to change all the methods into vitrual, or all classes have interfaces.
Search
First search: mock non-virtual, found an article:Portal
This article mentions an artifact:TypemockIt seems that it can be implemented, but it is charged...
Let's roughly look at its principles, andPostsharp(Postsharp is usedAOPWill modify the compiled DLL files, simple and crude!
Although rough, it seems to be a good method. Other projects are compiled normally. In the test project, all the methods are added with the virtual keyword for testing. Isn't that enough?
The idea is clear. Unfortunately, all the tools are charged until you seeLao Zhao's blog.
- Http://blog.zhaojie.me/2012/01/make-things-mockable-with-mono-cecil.html
- Http://www.findex.cn/item.php? Id = 421685
The above link is the original text of Lao Zhao. Unfortunately, he seems to have misoperations and is replaced by another article.
The following link is reprinted by others. You can see that even though the code is not indented.
In addition, Lao Zhao's article provides a good idea, but there is no detailed operation details in the future. I have been exploring it for a long time. Therefore, the following describes the complete and specific implementation steps.
Implementation ideas and technical details
In fact, the mock tool for billing has provided:
- In the project, write your own code according to the previous design principles;
- After each compilation of the test project, run a program to modify the DLL that requires mock;
- Use Moq and other mock frameworks to dynamically generate proxy classes during runtime;
Here, only the DLL copied to the test running directory will be modified, so other projects will not be affected.
For technical details, we need to useMono. CecilYes. We recommend that you useNugetObtain the latest version.
Mono. Cecil can help you modify compiled DLL files.
The core code is as follows (this logic is provided by Lao Zhao and I have made some modifications ):
private static void OverWrite(string file, bool hasSymbols){ var asmDef = AssemblyDefinition.ReadAssembly(file, new ReaderParameters { ReadSymbols = hasSymbols }); var classTypes = asmDef.Modules .SelectMany(m => m.Types) .Where(t => t.IsClass) .ToList(); foreach (var type in classTypes) { if (type.IsSealed) { type.IsSealed = false; } foreach (var method in type.Methods) { if (method.IsStatic) continue; if (method.IsConstructor) continue; if (method.IsAbstract) continue; if (!method.IsVirtual) { method.IsVirtual = true; method.IsNewSlot = true; method.IsReuseSlot = false; } else { method.IsFinal = false; } } } asmDef.Write(file, new WriterParameters { WriteSymbols = hasSymbols });}
You just need to encapsulate this code into a console application and run it after each compilation of the test project.
Detailed implementation steps source code Solution Structure
Mockhelper is the core tool and serves to modify the compiled DLL. Generally, you only need to use this tool. Other projects are only used for demonstration.
Testdll contains a sealed class and a non-virtual function, which will be used for demonstration later to convert it into mock.
The test project isMstestProject, which demonstrates how to use mockhelper.
The nunit project is also a demo test project, but it usesNunit.
Use of mockhelper
This console application is actually not difficult, and the core code has been posted above.
In addition, you need to copy mockhelper.exe1_mock.txt and mono. Cecil *. DLL to your test project. There are six files in total.
You can directly run the console application and input a parameter: the folder where the DLL is located. If no parameter is set, the parameter is in the running directory by default.
Write all the DLL files you need to modify to mock.txt.
Configure to automatically run mockhelper
The key to copying mockhelper is to make the EXE run automatically!
Here, we use the command line for generating events later.
Right-click Project-properties-generate event command line later:
"$(ProjectDir)MockHelper\MockHelper.exe"
You do not need to pass the parameter here, because the tool is running the test project, and the default running position of this project is bin/debug | release, so the DLL to be modified is below.
Write test code
Testdll is a non-virtual function and is a seal class:
public sealed class TestClass : TestClassBase{ public string NormalMethod() { return "TestClass"; } public override string VirtualMethod() { return base.VirtualMethod(); } public sealed override string SealedMethod() { return base.VirtualMethod(); } public override string AbstractMethod() { return "TestClass"; }}public abstract class TestClassBase{ public virtual string VirtualMethod() { return "TestClass"; } public virtual string SealedMethod() { return "TestClass"; } public abstract string AbstractMethod();}
The test code is as follows:
[TestClass]public class UnitTest{ [TestMethod] public void TestMethod1() { var test = new Mock<TestClass>(); test.Setup(t => t.NormalMethod()).Returns("Mock"); test.Setup(t => t.VirtualMethod()).Returns("Mock"); test.Setup(t => t.SealedMethod()).Returns("Mock"); test.Setup(t => t.AbstractMethod()).Returns("Mock"); Assert.AreEqual(test.Object.NormalMethod(), "Mock"); Assert.AreEqual(test.Object.VirtualMethod(), "Mock"); Assert.AreEqual(test.Object.SealedMethod(), "Mock"); Assert.AreEqual(test.Object.AbstractMethod(), "Mock"); }}
The mstest running result is as follows:
The nunit running result is as follows:
After removing this tool, the following error will be reported:
Notes
Don't look at the simple steps above. I made a lot of detours during configuration. Here I will share with you:
Be sure to use Moq and other mock frameworks
Why is automatic mock framework required? Does its core concern inherit a class?
This tool modifies the Il Code only after the code is compiled. That is to say, when writing, it is still a sealing class or a non-virtual method.
Therefore, if you write it yourself, it cannot be compiled.
Why does the automatic mock framework work?
Because these frameworks dynamically generate a class at runtime to inherit the classes that require mock. This class has been modified during running, so no error will occur.
Configure mstest
At the very beginning, I encountered a very tangled problem.
It works in my demo, but it keeps making mistakes in real projects.
After research, we found that in the project with errors, the test running directory is not in Bin/debug,
Instead in testresults/dozer_DOZER-PC 2012-11-27 11_11_22/out
In addition, this folder will create a new one each time you run the test. The DLL in is not copied from bin/debug, so the modified DLL in my tool does not play a role.
But why is this not in my demo? Later, we found that a later project was enabled.Test deploymentFunction, although you do not know the specific use of this function, the project that fails after cancellation is normal!
Cancel Method: Test-edit test settings-local (the other must also be configured)-deploy-Disable deployment.
Note! The configuration can take effect only after being canceled at the same time.
Are all test frameworks supported?
In principle, as long as you have a way to run the tool before running the test, you can support all the test frameworks.
As you can see above, the mstest and nunit configuration methods are exactly the same.
After testing, our company's automated deployment and testing framework can support this, and other environments may need some modifications and configurations, which is not very difficult.
Last
Project address: https://github.com/dozer47528/MockHelper
Finally, I would like to thank Lao Zhao for his ideas! Here is actually a specific implementation.
In fact, this is a helpless move. We 'd better honestly use more interfaces!