I have an extension method with the following signature (in BuildServerExtensions class)::
我有一个带有以下签名的扩展方法(在BuildServerExtensions类中)::
public static IEnumerable<BuildAgent> GetEnabledBuildAgents(
this IBuildServer buildServer,
string teamProjectName)
{
// omitted agrument validation and irrelevant code
var buildAgentSpec = buildServer.CreateBuildAgentSpec(teamProjectName);
}
And another method which calls the first (in BuildAgentSelector class):
另一个调用第一个方法(在BuildAgentSelector类中):
public BuildAgent Select(IBuildServer buildServer, string teamProjectName)
{
// omitted argument validation
IEnumerable<BuildAgent> serverBuildAgents =
buildServer.GetEnabledBuildAgents(teamProjectName);
// omitted - test doesn't get this far
}
And I am trying to test it using MSTest and Rhino.Mocks (v3.4) with:
我正在尝试使用MSTest和Rhino.Mocks(v3.4)测试它:
[TestMethod]
public void SelectReturnsNullOnNullBuildAgents()
{
Mocks = new MockRepository();
IBuildServer buildServer = Mocks.CreateMock<IBuildServer>();
BuildAgentSelector buildAgentSelector = new BuildAgentSelector();
using (Mocks.Record())
{
Expect.Call(buildServer.GetEnabledBuildAgents(TeamProjectName)).Return(null);
}
using (Mocks.Playback())
{
BuildAgent buildAgent = buildAgentSelector.Select(buildServer, TeamProjectName);
Assert.IsNull(buildAgent);
}
}
When I run this test I get:
当我运行这个测试时,我得到:
System.InvalidOperationException
:Previous method
IBuildServer.CreateBuildAgentSpec("TeamProjectName");
requires a return value or an exception to throw.上一个方法IBuildServer.CreateBuildAgentSpec(“TeamProjectName”);需要返回值或抛出异常。
This is obviously calling the real extension method rather than the test implementation. My next inclination was to try:
这显然是调用真正的扩展方法而不是测试实现。我的下一个倾向是尝试:
Expect.Call(BuildServerExtensions.GetEnabledBuildAgents(buildServer, TeamProjectName))
.Return(null);
Then I noticed that my expectations for Rhino.Mocks to intercept this were probably misplaced.
然后我注意到我对Rhino.Mocks拦截这个的期望可能是错位的。
The question is: How do I eliminate this dependency and make the Select method testable?
问题是:如何消除此依赖性并使Select方法可测试?
Note that the extension method and BuildAgentSelector classes are in the same assembly and I would prefer avoiding changing this or having to turn to something besides an extension method, though another mocking framework is something I would consider if I knew it would handle this situation.
请注意,扩展方法和BuildAgentSelector类在同一个程序集中,我宁愿避免更改它或不得不转向除扩展方法之外的东西,尽管如果我知道它会处理这种情况,我会考虑另一个模拟框架。
3 个解决方案
#1
Your extension method is actually written fairly well. Its a side-effect free method, and is extending an interface, rather than a concrete class. Your almost there, but you just need to go a little farther. You are trying to mock the .GetEnabledBuildAgents(...) extension method...however thats not actually mockable (by anything except TypeMock Isolator, which is the only thing that can actually mock statics at the moment...however its fairly pricy.)
您的扩展方法实际上写得很好。它是一种无副作用的方法,并且正在扩展接口,而不是具体的类。你几乎就在那里,但你只需要走得更远一点。你试图模拟.GetEnabledBuildAgents(...)扩展方法......但是这实际上并不是可模拟的(除了TypeMock Isolator之外的任何东西,这是目前唯一可以实际模拟静态的东西......但它相当昂贵。)
You are actually interested in mocking the method on IBuildAgent that your extension method calls internally: .CreateBuildAgentSpec(...). If you think it through, mocking the CreateBuildAgentSpec method will solve your problem. The extension method is "pure", and so really doesn't need to be mocked. It has no state and causes no side effects. It calls a single method on the IBuildAgent interface...which is the first clue that directs you to what really needs to be mocked.
您实际上有兴趣在IBuildAgent上模拟扩展方法在内部调用的方法:.CreateBuildAgentSpec(...)。如果您仔细考虑,模拟CreateBuildAgentSpec方法将解决您的问题。扩展方法是“纯粹的”,所以实际上不需要嘲笑。它没有状态,也没有副作用。它在IBuildAgent接口上调用单个方法...这是指导您真正需要模拟的第一个线索。
Try the following:
请尝试以下方法:
[TestMethod]
public void SelectReturnsNullOnNullBuildAgents()
{
Mocks = new MockRepository();
IBuildServer buildServer = Mocks.CreateMock<IBuildServer>();
BuildAgent agent = new BuildAgent { ... }; // Create an agent
BuildAgentSelector buildAgentSelector = new BuildAgentSelector();
using (Mocks.Record())
{
Expect.Call(buildServer.CreateBuildAgentSpec(TeamProjectName)).Return(new List<BuildAgent> { agent });
}
using (Mocks.Playback())
{
BuildAgent buildAgent = buildAgentSelector.Select(buildServer, TeamProjectName);
Assert.IsNull(buildAgent);
}
}
By creating a BuildAgent instance, and returning it in a List<BuildAgent>, you effectively return an IEnumerable<BuildAgent> that your Select method may operate on. That should get you going. You may need to do some additional mocking in case simply returning a basic BuildAgent instance isn't sufficient, or in case you need more than one. When it comes to mocking results to be returned, Rhino.Mocks can be a REAL pain in the rear to work with. If you run into troubles (which, given my experience with it, you are quite likely to), I recommend you give Moq a try, as it is a much nicer and more tester-friendly framework to work with. It doesn't require a repository, and eliminates the Record/Playback and using() statement heavy notation that Rhino.Mocks required. Moq also provides additional capabilities that other frameworks don't offer yet that, once you get into heavier mocking scenarios, you will fall in love with (i.e. the It.* methods.)
通过创建BuildAgent实例并在List
Hope this helps.
希望这可以帮助。
#2
After a break and coming back with a fresh head, I realized that I am actually mixing concerns in the BuildAgentSelector class a little. I am getting the agents and selecting them. By separating these two concerns and passing the agents to select directly to the BuildAgentSelector constructor (or a delegate/interface to do so), I am able to separate the concerns, remove the dependencies on both the buildServer and teamProjectName parameters, and simplify the interface in the process. It also achieved the testability results I was looking for on the BuildAgentSelector class. I can test the extension method separately nicely as well.
经过一段时间的休息并重新回头,我意识到我实际上在BuildAgentSelector类中混淆了一些问题。我正在接受代理并选择它们。通过分离这两个问题并传递代理直接选择BuildAgentSelector构造函数(或代理/接口),我能够分离关注点,删除buildServer和teamProjectName参数的依赖关系,并简化界面进行中。它还实现了我在BuildAgentSelector类中寻找的可测试性结果。我也可以很好地单独测试扩展方法。
However, in the end, it merely transferred the testing problem elsewhere. It is better because the concern is better placed, but jrista's answer resolves the issue no matter where the concern is placed.
但是,最后,它只是将测试问题转移到其他地方。这是更好的,因为关注点更好,但无论问题在何处,jrista的答案都能解决问题。
It is still a bit ugly to have to mock the second layer beneath the code under test. I essentially have to take the mock of the successful path from my extension method testing and reuse this code in my other tests - not difficult, but a bit annoying.
在测试代码下面模拟第二层仍然有点难看。我基本上必须从我的扩展方法测试中获取成功路径的模拟,并在我的其他测试中重用此代码 - 并不困难,但有点烦人。
I will give MOQ a try and be careful about getting too happy with writing extension methods.
我会尝试MOQ,并且对编写扩展方法感到非常满意。
#3
The test calls the real extension method because that is the only one. No test implementation is created when you mock an IBuildServer because the method is not a member of IBuildServer.
测试调用真正的扩展方法,因为这是唯一的方法。模拟IBuildServer时不会创建测试实现,因为该方法不是IBuildServer的成员。
There is no clean solution to this using the setup you have now.
使用您现在的设置,没有干净的解决方案。
Theoretically TypeMock will mock the static class, but refactoring out the extension methods would provide greater testability.
从理论上讲,TypeMock将模拟静态类,但重构扩展方法将提供更大的可测试性。
#1
Your extension method is actually written fairly well. Its a side-effect free method, and is extending an interface, rather than a concrete class. Your almost there, but you just need to go a little farther. You are trying to mock the .GetEnabledBuildAgents(...) extension method...however thats not actually mockable (by anything except TypeMock Isolator, which is the only thing that can actually mock statics at the moment...however its fairly pricy.)
您的扩展方法实际上写得很好。它是一种无副作用的方法,并且正在扩展接口,而不是具体的类。你几乎就在那里,但你只需要走得更远一点。你试图模拟.GetEnabledBuildAgents(...)扩展方法......但是这实际上并不是可模拟的(除了TypeMock Isolator之外的任何东西,这是目前唯一可以实际模拟静态的东西......但它相当昂贵。)
You are actually interested in mocking the method on IBuildAgent that your extension method calls internally: .CreateBuildAgentSpec(...). If you think it through, mocking the CreateBuildAgentSpec method will solve your problem. The extension method is "pure", and so really doesn't need to be mocked. It has no state and causes no side effects. It calls a single method on the IBuildAgent interface...which is the first clue that directs you to what really needs to be mocked.
您实际上有兴趣在IBuildAgent上模拟扩展方法在内部调用的方法:.CreateBuildAgentSpec(...)。如果您仔细考虑,模拟CreateBuildAgentSpec方法将解决您的问题。扩展方法是“纯粹的”,所以实际上不需要嘲笑。它没有状态,也没有副作用。它在IBuildAgent接口上调用单个方法...这是指导您真正需要模拟的第一个线索。
Try the following:
请尝试以下方法:
[TestMethod]
public void SelectReturnsNullOnNullBuildAgents()
{
Mocks = new MockRepository();
IBuildServer buildServer = Mocks.CreateMock<IBuildServer>();
BuildAgent agent = new BuildAgent { ... }; // Create an agent
BuildAgentSelector buildAgentSelector = new BuildAgentSelector();
using (Mocks.Record())
{
Expect.Call(buildServer.CreateBuildAgentSpec(TeamProjectName)).Return(new List<BuildAgent> { agent });
}
using (Mocks.Playback())
{
BuildAgent buildAgent = buildAgentSelector.Select(buildServer, TeamProjectName);
Assert.IsNull(buildAgent);
}
}
By creating a BuildAgent instance, and returning it in a List<BuildAgent>, you effectively return an IEnumerable<BuildAgent> that your Select method may operate on. That should get you going. You may need to do some additional mocking in case simply returning a basic BuildAgent instance isn't sufficient, or in case you need more than one. When it comes to mocking results to be returned, Rhino.Mocks can be a REAL pain in the rear to work with. If you run into troubles (which, given my experience with it, you are quite likely to), I recommend you give Moq a try, as it is a much nicer and more tester-friendly framework to work with. It doesn't require a repository, and eliminates the Record/Playback and using() statement heavy notation that Rhino.Mocks required. Moq also provides additional capabilities that other frameworks don't offer yet that, once you get into heavier mocking scenarios, you will fall in love with (i.e. the It.* methods.)
通过创建BuildAgent实例并在List
Hope this helps.
希望这可以帮助。
#2
After a break and coming back with a fresh head, I realized that I am actually mixing concerns in the BuildAgentSelector class a little. I am getting the agents and selecting them. By separating these two concerns and passing the agents to select directly to the BuildAgentSelector constructor (or a delegate/interface to do so), I am able to separate the concerns, remove the dependencies on both the buildServer and teamProjectName parameters, and simplify the interface in the process. It also achieved the testability results I was looking for on the BuildAgentSelector class. I can test the extension method separately nicely as well.
经过一段时间的休息并重新回头,我意识到我实际上在BuildAgentSelector类中混淆了一些问题。我正在接受代理并选择它们。通过分离这两个问题并传递代理直接选择BuildAgentSelector构造函数(或代理/接口),我能够分离关注点,删除buildServer和teamProjectName参数的依赖关系,并简化界面进行中。它还实现了我在BuildAgentSelector类中寻找的可测试性结果。我也可以很好地单独测试扩展方法。
However, in the end, it merely transferred the testing problem elsewhere. It is better because the concern is better placed, but jrista's answer resolves the issue no matter where the concern is placed.
但是,最后,它只是将测试问题转移到其他地方。这是更好的,因为关注点更好,但无论问题在何处,jrista的答案都能解决问题。
It is still a bit ugly to have to mock the second layer beneath the code under test. I essentially have to take the mock of the successful path from my extension method testing and reuse this code in my other tests - not difficult, but a bit annoying.
在测试代码下面模拟第二层仍然有点难看。我基本上必须从我的扩展方法测试中获取成功路径的模拟,并在我的其他测试中重用此代码 - 并不困难,但有点烦人。
I will give MOQ a try and be careful about getting too happy with writing extension methods.
我会尝试MOQ,并且对编写扩展方法感到非常满意。
#3
The test calls the real extension method because that is the only one. No test implementation is created when you mock an IBuildServer because the method is not a member of IBuildServer.
测试调用真正的扩展方法,因为这是唯一的方法。模拟IBuildServer时不会创建测试实现,因为该方法不是IBuildServer的成员。
There is no clean solution to this using the setup you have now.
使用您现在的设置,没有干净的解决方案。
Theoretically TypeMock will mock the static class, but refactoring out the extension methods would provide greater testability.
从理论上讲,TypeMock将模拟静态类,但重构扩展方法将提供更大的可测试性。