Reading directory:
1. Introduction
Recently, I ended the development of a small project. I think some good things are worth summarizing and sharing, so I will spend some time organizing them into articles;
In most cases, we all know these concepts. Interface-oriented programming is a common topic, and many people who have years of programming experience know how to use it. In fact, unit testing has not been very important in the past few years, however, we are gradually catching up in front of us recently, and the frequency of being mentioned is also quite high, including refactoring and testability, we can understand its essence only by using it in person;
Next I will summarize my new experiences with these concepts;
2) iterative testing and refactoring: mandatory interface-oriented programming, requiring code testability.) Brief Introduction to interface-oriented programming]
Interface-oriented programming requires that we use interfaces to call each other and isolate all the instances that may change. These instances are just the ones that can be replaced at any time; however, interface-oriented programming requires a certain degree of design capability. Whether or not the object can be reasonably abstracted out of the interface cannot be summarized in one sentence or two;
In fact, I think there will be some detailed design misunderstandings in interface-oriented design. Since an interface is abstracted, there will be interface dependencies, andEntityIs the type abstraction reasonable? Will it disrupt?EntityBecause our understanding of the DomainModel is that DomainEntity is a POCO object, which is a very simple and pure class entity, if you switch to an interface, it will be a great deal of trouble for the development of the following DDD, because the support for the interface cannot be simple and persistent, and there is also a great deal of trouble for the transformation of thinking;
2.1 Two design misunderstandings for Interface Programming
First of all, I think the first misunderstanding is the interface dependency problem. The interface dependency is not a small problem, and there are strict requirements for dependencies between real project middle layers, the traditional layered architecture requires that the upper layer only rely on the lower layer, while the DDD layered architecture requires no dependency on the DomaiModel layer, and the DomainModel does not reference the lower layer infrastructure because it requires absolute cleanliness; however, it is found that there are still many projects that cannot understand the advantages of DDD. Then, for the entity extraction interfaces between layers, this is really to be discussed, the data entities in DataAccess Layer strictly mean that DTO objects are used in Business Layer for excessive use. If you design DTO in DataAccess as an interface type for external use, the Business Layer depends on DataAccess Layer. Therefore, we need to balance the data according to the specific needs of the project. Let's take a look at the example and analysis below;
2.1.1 interface Dependency inversion
In the traditional three-tier architecture, BLL calls the BLL method in Facade and BLL calls the DAL method. Isn't it against the "single responsibility" principle; we have been emphasizing the design principle of "single responsibility". Why do many projects directly use lower-layer interfaces between each layer, especially in our core DomainModel layer, originally, it was a very clean and pure service processing. What data access interface is really not beautiful;
Figure 1:
650) this. length = 650; "src =" http://www.bkjia.com/uploads/allimg/131228/155Fa3X-0.jpg "title =" 1.jpg" width = "658" height = "454" border = "1" hspace = "0" vspace = "0" style = "width: 658px; height: 454px; "/>
This architecture should be the structure of most projects. We should see at a glance where the problem is. Obviously, the Da Layer interface is directly used in the Bl Layer to obtain data, from this point alone, it is somewhat contrary to the design principle of a single responsibility;
Figure 2:
650) this. length = 650; "src =" http://www.bkjia.com/uploads/allimg/131228/155F932H-1.jpg "title =" 2.jpg" width = "748" height = "455" border = "1" hspace = "0" vspace = "0" style = "width: 748px; height: 455px; "/>
In the first figure, the business layer depends on the data layer. In the second figure, the business layer does not depend on anything, careful friends should see that there is a "DomainModel Event route" in the second figure, which is a mechanism to generate domain events inside the domain, similar to the effect of Event Routing, the infrastructure has nothing to do with DomaiModel Entity itself;
2.1.2 interface abstraction of Objects
It will be awkward to abstract objects into interfaces. Our most intuitive understanding of entities is a very POCO object, however, if it is unnecessary to design the DTO for data access as an interface during the design, you can balance this need in two situations, first, it doesn't matter if your DTO does not require the business layer to pass in the data layer. If you need the business layer to pass in the data layer interfaces, here, we think it is not intuitive to bring together the concepts of entities and interfaces. For business entities, it is a problem for you to extract layer-based interfaces for persistence;
2.2) iterative unit testing and code Refactoring can be tested)
In fact, the main content of this article is in this section. In the previous section, I spoke about my personal views on interface abstraction; in this section, we will look at the important content of this article through a specific example to see how unit testing works perfectly with continuous iteration refactoring, when writing unit test cases, we will find that the code is gradually restructured very beautifully, and interface-oriented programming is once again mentioned a height;
When we write code, we usually cannot verify whether our code is good or bad. It is difficult to determine whether everyone's design ideas are completely correct simply by mouth, so code testability will become an important indicator to verify the quality of the code you write;
Unit testing and refactoring will be a continuous iteration process. Many people are not very concerned about refactoring and unit testing, in fact, in most cases, we are developing a one-time delivery project instead of a continuously updated product, so unit testing and refactoring are ignored by us, interface-Oriented Programming is also often remembered and forgotten. Let's take a look at how to write testable code;
/* ===================================================== ========================================================== * Author: deep training * Create time: 2013-08-24 * Blog Address: http://www.cnblogs.com/wangiqngpei557/ * Author Description: software engineering practices in specific fields; * ===================================================== ======================================================= * /namespace UnittestDemo {using System. linq. expressions; using System; public static class ServiceReport {public static Report QueryReport (string queryWhere) {return new Report ();}}}
This is a very simple static class. It mainly aims to simulate querying related Report information from the server based on the query conditions. Because the Report object is returned for demonstration, it is only used as an example, report is an abstract Report object without any data fields;
/* ===================================================== ========================================================== * Author: deep training * Create time: 2013-08-24 * Blog Address: http://www.cnblogs.com/wangiqngpei557/ * Author Description: software engineering practices in specific fields; * ===================================================== ======================================================= * /namespace UnittestDemo {using System; public class ReportAnalyse {public bool Analyse (DateTime dt) {ServiceReport. queryReport (string. format ("State = {0}", 1); return true ;}}}
This is an instance class used to analyze the remotely returned expression. It is like a business with data access. However, in most cases, we use static classes for data access;
/* ===================================================== ========================================================== * Author: deep training * Create time: 2013-08-24 * Blog Address: http://www.cnblogs.com/wangiqngpei557/ * Author Description: software engineering practices in specific fields; * ===================================================== ======================================================= * /namespace UnittestDemo {using System; public class AppStart {public static void MainStart () {ReportAnalyse analyze = new ReportAnalyse (); bool result = analyze. analyse (DateTime. now); if (result) {//} else {//}}}}
This is where the program is called. It is used to simulate the entry at the time of running the program. It can be regarded as a Facade object in the Application Layer;
In fact, here we can see the "single responsibility" design principle I mentioned in my summary in 2.1. I have used the data access code in ReportAnalyse. In fact, this is not true, it should be loaded externally and passed in to ReportAnalyse to comply with the single responsibility design principle. Of course, this is not about it, so don't talk about it;
Let's assume that the above Code has completed the analysis of the Report object. Next we need to perform a unit test on the code, mainly in two classes: ReportAnalyse and ServiceReport. Let's start with the ReportAnalyse class first;
Unit Test]
Create a basic unit test project, reference the tested project, and create a new unit test file to test the ReportAnalyse class;
using System;using Microsoft.VisualStudio.TestTools.UnitTesting;using UnittestDemo;namespace UnittestDemoUnit{ [TestClass] public class ReportAnalyseUnitTest { [TestMethod] public void ReportAnalyse_Analyse_UnitTest() { ReportAnalyse testReportAnalyse = new ReportAnalyse(); bool result = testReportAnalyse.Analyse(DateTime.Now); Assert.IsTrue(result); } }}
Write a simple test case. The main purpose here is not to write a test case, nor to test the code. The purpose here is to perform iterative processes such as unit testing and restructuring, therefore, writing a use case is not the focus. It is included here;
Figure 3:
650) this. length = 650; "src =" http://www.bkjia.com/uploads/allimg/131228/155F95947-2.jpg "title =" 3.jpg" width = "423" height = "111" border = "1" hspace = "0" vspace = "0" style = "width: 423px; height: 111px; "/>
If there is no problem, this unit test case must have passed, because there is no other logic, very simple two lines of code; it looks good together, no problem, the unit test also passed, at this time, we were relieved to do other functions, but a few days later we found that our ReportAnalyse unit test suddenly failed. Later we checked that someone had changed the ServiceReport implementation, the originally locally instantiated Report must be configured before it can be used. That is to say, you cannot test your code at this time, so that your ReportAnalyse will be affected by ServiceReport at any time, however, this problem does not matter if it is run. After all, it is configured on the production line;
At this time, it will be a systemic dilemma, because our code is oriented to implementation programming, that is to say, the coupling degree is very high. In this case, we need to properly refactor ServiceReport as needed, of course, the primary goal of refactoring is to decouple it from any implementations;
Next we will extract an interface from ServiceReport, and then inject it dynamically through IOC to achieve full decoupling;
/* ===================================================== ========================================================== * Author: deep training * Create time: 2013-08-24 * Blog Address: http://www.cnblogs.com/wangiqngpei557/ * Author Description: software engineering practices in specific fields; * ===================================================== ======================================================= * /namespace UnittestDemo {using System; public class ReportAnalyse {IServiceReport serviceReport; public ReportAnalyse (IServiceReport serviceReport) {this. serviceReport = serviceReport;} public bool Analyse (DateTime dt) {serviceReport. queryReport (string. format ("State = {0}", 1); return true ;}}}
The constructor here is not directly instantiated and must be supported by the IOC framework. Let's take a look at the code above, which is very concise and dependent on the IServiceReport interface, at this time, we will go back and make simple modifications to the unit test to adapt to the code that can be continuously restructured;
In order to make the code test point better, I modified the Analyse method;
Figure 4:
650) this. length = 650; "src =" http://www.bkjia.com/uploads/allimg/131228/155Fa461-3.jpg "title =" 4.jpg" width = "586" height = "124" border = "1" hspace = "0" vspace = "0" style = "width: 586px; height: 124px; "/>
The red line will change with the changes of ServiceReport before we rebuild it. However, after we abstract it into an interface, it will be easy to test, we can control its return values by ourselves;
Figure 5:
650) this. length = 650; "src =" http://www.bkjia.com/uploads/allimg/131228/155F9Da-4.jpg "title =" 5.jpg" width = "772" height = "284" border = "1" hspace = "0" vspace = "0" style = "width: 772px; height: 284px; "/>
The code for unit test has changed a bit. The IServiceReport interface passed in from the constructor has been Mock. In fact, this is the first part of the unit test framework ,. the Fakes framework provided by NET itself is also very good. It will provide all the simulated code automatically generated in the background, and it is very good to combine with VisualStudioIDE;
At this time, we can control any behavior of the IServiceReport interface. Only by changing the implementation to the interface can Mock have the opportunity to insert logic;
According to such a unit test case, the case code cannot pass, because I have returned a null Report object, and here you can completely control any value of the person, therefore, your unit test class will not be subject to any external interference, so that your code can be tested;
As mentioned in the center of the article so far, we also see a simple example of how to find reasons for this design from interface-oriented programming, in fact, that is to say, interface-oriented programming will make the class testable. unit testing and refactoring are a continuous process, and the code is maintained every day, every day, people use unit test cases, which form a good iteration relationship;
Figure 6:
650) this. length = 650; "src =" http://www.bkjia.com/uploads/allimg/131228/155F9C47-5.jpg "title =" 6.jpg" width = "495" height = "248" border = "1" hspace = "0" vspace = "0" style = "width: 495px; height: 248px; "/>
In this way, the Code remains in a stable state, and the restructured code is verified through unit tests. New functions can also be verified in real time using unit tests;
2.2.1 Impact of a LINQ expression on unit test
We use quite a lot of LINQ, and it does a good job of processing collections. It is easy to write and coherent in thinking; however, for unit tests, you need to note that it should not be too long. If it is too long and difficult to perform tests, it is hard to achieve 100% coverage even if the code is covered, therefore, if we have two nested or more suggestions, we should divide them into two independent methods, so that the code can be easily tested. Even if we change it later, we will not be afraid of affecting other logics;
A good suggestion is to use a method to return the LINQ expression. The method is like a factory with the same conventions, and put the specific LINQ expression into a unified place for management;
Conclusion: In fact, I only know a little about unit testing and refactoring, But I have recently learned a little more about it. Therefore, it is a summary of the project, I think there is still a great reference value. Any new thing is very common when we do not study it, in fact, if we really study it, we will find that it is really surprising that everything will have the value of existence, depending on whether we need to use it; many projects, including my previous company's long-term maintenance of a project that can no longer be maintained, are due to the lack of reconstruction and testing, which has become today's situation. In a word from our company's leaders, will become the company's"Technical debt"Sooner or later, it will need to be changed; in fact, it will gradually become a huge resource consumption point and burden of the company;
Sample Code address: http://files.cnblogs.com/wangiqngpei557/UnittestDemo.zip
Author:Wang qingpei
Source:Http://wangqingpei557.blog.51cto.com/
The copyright of this article is shared by the author and BKJIA. You are welcome to repost this article. However, you must retain this statement without the author's consent and provide the original article connection clearly on the article page. Otherwise, you will be entitled to pursue legal liability.
This article is from the pattern driven the world blog, please be sure to keep this source http://wangqingpei557.blog.51cto.com/1009349/1282332