我正试图为一个看起来像这样的函数试验单元测试:
public List<int> Process(int input)
{
List<int> outputList = new List<int>();
List<int> list = this.dependency1.GetSomeList(input);
foreach(int element in list)
{
// ... Do some procssing to element
//Do some more processing
int processedInt = this.dependency2.DoSomeProcessing(element);
// ... Do some processing to processedInt
outputList.Add(processedInt);
}
return outputList;
}
我计划在测试用例中模拟dependency1和dependency2,但我不确定应该如何设置它们。为了设置dependency2.DoSomeProcessing,我需要知道每次调用“element”的值。为了解决这个问题,我要么:
将我的Process()方法中的一些逻辑复制粘贴到测试用例中
手动计算值(在实际函数中,这将涉及将一些带有许多小数位的双精度硬编码到测试用例中)
咬紧牙关并使用dependency2的实际实现,而不是模仿它。
这些解决方案对我来说都不是很好。我是IOC的新手并且嘲笑所以我希望有一些我完全没有得到的东西。如果不是这些解决方案中哪一个看起来最不好?
由于
编辑:我正在使用Moq来模拟依赖项。
答案 0 :(得分:1)
变量“element”是否在foreach和DoSomeProcessing(element)方法的开头之间发生了变化?如果没有,我会选择2号选项。由于你可以控制dependency1和dependency2,你可以为它们创建一个存根,这样你就可以返回列表中的一些元素,然后在dependency2中编写代码来处理你得到的每个元素。来自dependency1并返回正确的整数。
我的第二个选择是3.我不喜欢第一个。
顺便说一下,看看Pex(http://research.microsoft.com/en-us/projects/pex/)在这种情况下对你有用,因为你已经完成了实际的代码。
[]的
答案 1 :(得分:1)
你究竟在测试什么?
此方法的内容是它调用一个函数来获取值列表,然后调用另一个函数来修改这些值并返回修改后的值。
您不应该测试获取值的逻辑,或者在此测试中转换它的逻辑,它们是单独的测试。
所以你的嘲讽应该是(手工编写Moq)
var dependency1 = new Mock<IDependency1>()
.Setup(d => d.GetSomeList(It.IsAny<int>())
.Returns(new List<int>(new [] { 1, 2, 3 });
var dependency2 = new Mock<IDependency2>()
.Setup(d => d.DoSomeProcessing(It.IsAny<int>())
.Returns(x => x * 2); // You can get the input value and use it to determine an output value
然后运行您的方法并确保返回的列表是2,4,6。这验证了某些列表上执行了某些处理。
然后,您创建一个不同的测试,以确保GetSomeList()
有效。另一个确保DoSomeProcessing()
有效。它是DoSomeProcessing()
测试,需要手工计算的值。
答案 2 :(得分:1)
模拟两个依赖项
最好的解决方案是模拟两个依赖项。理想情况下,您的具体类应该在构造时注入这些依赖项。
你只需要在Process中测试3件事。
您永远不应该使用具体的依赖项,这将导致您进行脆弱的单元测试,因为您将依赖于您无法控制的数据。要正确测试方法,您需要提供无效输入(空值,空字符串,极大数字,负数)等,以试图打破当前预期的功能。并且自然也是有效的输入。
您也不需要复制依赖项的实际功能。您需要测试的唯一事项是沿着所有逻辑路径的代码的正确功能。如果需要更改dependency2的输出以测试第二个代码路径。那是另一个测试。
将测试分成大量细粒度测试用例的好处是,如果您在Process中更改任何内容,并运行单元测试。你确切知道问题的确切位置,因为3/50测试现在失败,你知道在哪里解决你的问题,因为他们都在测试1具体的事情。
使用Rhino Mocks将会是这样的
private IDependency1 _dependency1;
private IDependency2 _dependency2;
private ClassToBeTested _classToBeTested;
[SetUp]
private override void SetUp()
{
base.SetUp();
_dependency1 = MockRepository.GenerateMock<IDependency1>();
_dependency2 = MockRepository.GenerateMock<IDependency2>();
_classToBeTested = new ClassToBeTested(_dependency1, _dependency2);
}
[Test]
public void TestCorrectFunctionOfProcess()
{
int input = 10000;
IList<int> returnList = new List<int>() {1,2,3,4};
// Arrange
_dependency1.Expect(d1 => d1.GetSomeList(input)).Return(returnList);
_dependency2.Expect(d2 => d2.DoSomeProcessing(0))
.AtLeastOnce().IgnoreArguments().Return(1);
// Act
var outputList = _classToBeTested.Process(input);
// Assert that output is correct for all mocked inputs
Assert.IsNotNull(outputList, "Output list should not be null")
// Assert correct behavior was _dependency1.GetSomeList(input) called?
_dependency1.VerifyAllExpectations();
_dependency2.VerifyAllExpectations();
}
<强>更新强>
IElementProcessor _elementProcessor;
public List<int> Process(int input)
{
List<int> outputList = new List<int>();
List<int> list = this.dependency1.GetSomeList(input);
foreach(int element in list)
{
// ... Do some procssing to element
_elementProcessor.ProcessElement(element);
//Do some more processing
int processedInt = this.dependency2.DoSomeProcessing(element);
// ... Do some processing to processedInt
_elementProcessor.ProcessInt(processedInt);
outputList.Add(processedInt);
}
return outputList;
}
所以上面发生的事情是,你的两个处理现在被分解成单独的对象。过程几乎不是完全抽象的(这是完美的)。您现在可以单独测试每个元素,以确保在单独的单元测试中具有正确的功能。
然后,上面的测试将成为集成测试,您可以测试每个依赖项是否正确调用。
答案 3 :(得分:1)
好的,首先如果您可以访问此方法并且可以更改它,那么更好的方法如下:
public IEnumerable<int> Process(int input)
{
foreach(var element in dependency1.GetSomeList(input))
{
yield return dependency2.DoSomeProcessing(element);
}
}
现在谈谈手头的问题。您需要模拟方法中的2个依赖项才能正确测试它。有一些很好的方法可以做到这一点,但最简单的方法是使用Constructor Dependency Injection。这是通过构造函数将依赖项注入到类中的位置。您将需要您的依赖项具有公共基类型(接口或抽象类)来执行注入。
public class Processor
{
public Processor(IDep1 dependency1, IDep2 dependency2)
{
_dependency1 = dependency1;
_dependency2 = dependency2;
}
IDep1 _dependency1;
IDep2 _dependency2;
public IEnumerable<int> Process(int input)
{
foreach(var element in dependency1.GetSomeList(input))
{
yield return dependency2.DoSomeProcessing(element);
}
}
}
现在创建一个模拟注入它以进行测试。
public interface IDep1
{
IEnumerable<int> GetSomeList(int);
}
public interface IDep2
{
int DoSomeProcessing(int);
}
public class MockDepdency1 : IDep1
{
public IEnumerable<int> GetSomeList(int val)
{
return new int[]{ 1, 2, 3 }.AsEnumerable();
}
}
public class MockDepdency2 : IDep2
{
public int DoSomeProcessing(int val)
{
return val + 1;
}
}
...
main()
{
IDep1 dep1 = new MockDependency1();
IDep2 dep2 = new MockDependency2();
var proc = new Processor(dep1, dep2);
var actual = proc.Process(5);
Assert.IsTrue(actual.Count() == 3);
}
我没有在编译器中编写这段代码 - 我只是手工输入它,但它应该接近工作状态,并且代表了如何测试该方法的一个很好的例子。
更好的方法是使用像Unity这样的实用程序,而不是通过构造函数注入。相反,您可以将您的界面映射到测试中的MockDependency1和MockDependency2,当Process(...)运行时,它将获得Mock版本。如果你想知道如何做到这一点,请告诉我,我将在下面添加。