After you write a program written for a while will start to hear some of the terminology
Inversion of control (Inversion of Control, abbreviated IoC)
Dependency injection (Dependency Injection, abbreviated DI)
Perhaps coupled with a long time ago may no serious they get to know the interface (Interface)
About these things in the end mean I think everyone will be back to explain Chinese
But I probably just do not understand in the end the program get so complicated What are the benefits?
The program also had a good run so why do we always say we go to write?
I want to use the most simple example to be a description
Suppose we program divided into three layers
DB → (1) .Repository → (2) .Service → (3). The actual program
That is (3). The actual program can not be skipped (2) .Service direct access to (1) .Repository
Less likely to skip (2). Service + (1) .Repository directly touch DB
(1). Repository data actually provided one
=> Whether you write SQL, Stored Procedure, ORM ...... anyway you how the data in this layer is removed
(2). Service business logic
=> I suppose just one example of my real name called 'Chen Po' (in the presence DB) but because of a law of our foreign capital can only display 'Chen'
(3) The actual program
=> This is not to explain it ...
Our topics are as follows
DB There is a membership base data table which kept the members of a capital
I hope some of Hinako only show surname (two asterisks)
For ease of discussion
I DB, (1). Repository, (2). Service, (3). The actual program, all in the same file, and all are located public
In fact, they usually scattered in different cs file, or different projects, and not all are public, pay special attention to this point
We can quickly complete the first edition of the program
using System;
using System.Collections.Generic;
using System.Linq;
namespace MySample.Console
{
public class Sample
{
public int Id { get; set; }
public string Name { get; set; }
public DateTime Birthday { get; set; }
}
///
/// 实际存取DB
///
public class Repository
{
public List
GetSample(int param)
{
//为了方便讲解 (否则这些数据应该是从 DB 取出)
var source = new List
{
new Sample { Id = 1, Name = "陈大宝", Birthday = DateTime.Parse("2001/1/1") },
new Sample { Id = 2, Name = "林小宝", Birthday = DateTime.Parse("2001/2/1") },
new Sample { Id = 3, Name = "王聪明", Birthday = DateTime.Parse("2001/3/1") },
};
return source.Where(q => q.Id == param).ToList();
}
}
///
/// 透过Repository从DB取数据后 做一些商业逻辑
///
public class Service
{
Repository repo = new Repository();
public List
GetSample(int param)
{
var samples = repo.GetSample(param);
//假设有一个商业逻辑是大家不能知道会员的全名 只能知道 姓氏 +
foreach (var item in samples)
{
if (item.Name != null && item.Name.Length >=1 )
{
item.Name = item.Name.Substring(0, 1) + "";
}
}
return samples;
}
}
///
/// DB → Repository → Service → 实际程序
///
class Program
{
static void Main(string[] args)
{
Service service = new Service();
var result = service.GetSample(1);
}
}
}
This program is the perfect solution to our problems
But we found from the program
1. The actual program dependent on the Service ... The first line I was new Service ()
2. Service Repository ... dependent on the first line I new Repository ()
This program is certainly not what Inversion of Control will not be a dependency injection but so what my program is still running well did not cause distress
The story will certainly be some turning point
One day someone your reaction procedure is not wrong?
He said we'd show how Chen yesterday from midnight Chen start page all become big treasure!?!?!?!?
oh it can be incredible
Showed off the full name to a violation of a domestic law right away I have to measure what this program there is no problem
But this program you watched the measure you where to play?
1. Repository dependent on DB
=> This fall I simply altered connection parameter refers to the config in my test DB manually can plug some fake data
2. Service dependent on the Repository
=> Ah ... how to think like a temporary measure ... but it does not matter anyway, now is not the issue here is not the type 'Chen Po' has taken data 'Andy Lin' of
3. The actual program dependent on Service
=> This is the trouble spot !!! Hinako become the program in this !!
Although being no way to come up with all the answers but at least we now have a solution
Solution as follows:
1. DB test to do a fake data (Id = 1, Name = Chen Po) and then put into a test config DB connection string
2. From actual program input parameters Id = 1, run a Service.GetSample ()
You can know what went wrong!
But why set up a test which would allow it?
The original Repository dependent on DB
But Repository is through the connection string to open DB
Repository Entity it is actually injected into the connection string (in the config)
So when I modified the connection string => pass into the Repository of the things is not the same => Let's become the official database test database
That Repository → connection string (can be manual operation) → DB ... is because this design allows us to manipulate config test
Then you change to the tester later if unfortunately found today really is output instead of Chen Chen Tai Po
But damn this month to have 10 PG into the 20 version of the program becomes 10 million lines
public class Repository
{
public List
GetSample(int param)
{
//多了两百行程序
//好像又多开 4 张 Table
//我也看不懂他到底在串什么
//该不会其实是这里坏掉吧?
return source.Where(q => q.Id == param).ToList();
}
}
public List
GetSample(int param)
{
//最近上版10次增加了500行!!!!
var samples = repo.GetSample(param);
//为什么这里又要去call一些我觉得根本用不到的函数?
foreach (var item in samples)
{
if (item.Name != null && item.Name.Length >=1 )
{
item.Name = item.Name.Substring(0, 1) + "";
//做这些运算不是没意义吗? 为什么这里又多了50行???????
}
}
//上次他们好像说有个项目要加新功能 所以这里的100行是在搞那个功能?????
return samples;
}
While I'm sure the program through the switch test DB was really a mess out
I can only identify problems but is not DB (DB because I was connected to the test environment manually fraud (Mock) a)
That question in the end is the most recent version was changed Repository bad? Service is changed or bad?
I just like there is no way to change the connection string as first and then exclude a possibility?
I can do a Repository layer known certainly wrong to manually do?
Just like my own hand key test as DB let me rule out a possibility of an option?
So we look at the second edition of the program
First Nuget download Moq
Partially changed as follows
Repository Layer
Service Layer
My program
test program
Complete the following procedure
using Moq;
using System;
using System.Collections.Generic;
using System.Linq;
namespace MySample.Console
{
public class Sample
{
public int Id { get; set; }
public string Name { get; set; }
public DateTime Birthday { get; set; }
}
public interface IRepository
{
List
GetSample(int param);
}
///
/// 实际存取DB
///
public class Repository : IRepository
{
public List
GetSample(int param)
{
//为了方便讲解 (否则这些数据应该是从 DB 取出)
var source = new List
{
new Sample { Id = 1, Name = "张小华", Birthday = DateTime.Parse("2001/1/1") },
new Sample { Id = 2, Name = "林小宝", Birthday = DateTime.Parse("2001/2/1") },
new Sample { Id = 3, Name = "王聪明", Birthday = DateTime.Parse("2001/3/1") },
};
return source.Where(q => q.Id == param).ToList();
}
}
///
/// 透过Repository从DB取数据后 做一些商业逻辑
///
public class Service
{
IRepository repo;
public Service(IRepository pRepo)
{
repo = pRepo;
}
public List
GetSample(int param)
{
var samples = repo.GetSample(param);
//假设有一个商业逻辑是大家不能知道会员的全名 只能知道 姓氏 +
foreach (var item in samples)
{
if (item.Name != null && item.Name.Length >=1 )
{
item.Name = item.Name.Substring(0, 1) + "";
}
}
return samples;
}
}
///
/// DB → Repository → Service → 实际程序 ///
class Program
{
static void Main(string[] args)
{
Service service = new Service(new Repository());
var result = service.GetSample(1);
var test = MyTest();
System.Diagnostics.Debug.WriteLine("Hello World!");
}
///
/// 假设这是一个test case ///
///
public static bool MyTest()
{
//arrange
var source = new List
{ new Sample { Id = 1, Name = "陈大宝", Birthday = DateTime.Parse("2001/1/1") }, }; var mockRepo = new Mock
(); mockRepo.Setup(repo => repo.GetSample(It.IsAny
())).Returns(source); var service = new Service(mockRepo.Object); //act var actual = service.GetSample(1); //assert return actual.First().Name == "陈"; } } }
Finally, we put together just about what happens
Config connection strings of hair like
A. If the source can be swappable (DB official with the test DB) => inversion of control
B. by changing the config => dependency injection
C. Because the truth is exactly the same DB are able to achieve one-liners do not change can not verify program
=> Official test DB and DB's Table Schema exactly the same
=> Inherit the same interface IRepo true Repo (the original program) and false Repo (Mock) and also the same structure
=> Line so the program will not change anything
in conclusion
To test the program (Service) in
The change will become due (Repository) I have been brought under control
So when the results of error (rather than output Chen Po Chen)
I can determine the Service wrong
因为 Repository 从头到尾就根本没跑 他是一个被我控制住的变因 是我自己造假结果 ( Mock ) 以后传进去的
从头到尾就只有Service在变 所以若结果出错 则 Service 必然有问题
这也就是控制反转、相依性注入之所以有意义的地方
原文:大专栏 [控制反转]以Mock进行Unit Test为例