我知道如何使用这些术语,但我想知道单元测试是否有伪造、模拟和存根的公认定义?如何为您的测试定义这些?描述一下你可能会用到它们的场景。
以下是我如何使用它们:
Fake:实现接口但包含固定数据且没有逻辑的类。只是根据实现返回“好”或“坏”数据。
Mock:实现接口的类,允许从特定方法动态设置返回值/抛出异常,并提供检查特定方法是否被调用/未调用的能力。
Stub:类似于模拟类,只是它不提供验证方法是否被调用的能力。
模拟和存根可以手工生成,也可以由模拟框架生成。假类是手工生成的。我使用模拟主要是为了验证我的类和依赖类之间的交互。一旦我验证了交互并在代码中测试替代路径,我就使用存根。我使用伪类主要是为了抽象出数据依赖关系,或者当mock /存根太乏味而每次都无法设置时。
Stub, Fakes和Mocks在不同的来源中有不同的含义。我建议你介绍一下你的团队内部术语,并就其含义达成一致。
我认为区分两种方法很重要:
-行为验证(暗示行为替换)
-最终状态验证(暗示行为模拟)
考虑发送电子邮件以防出错。当做行为验证时,你检查IEmailSender的Send方法是否执行了一次。并且您需要模拟此方法的返回结果,返回发送消息的Id。所以你说:“我期望Send会被调用。我只会为任何调用返回虚拟(或随机)Id”。这就是行为验证:
emailSender.Expect(es=> . send (anyThing)).Return((subject,body) => "dummyId")
在进行状态验证时,您需要创建实现IEmailSender的TestEmailSender。并实现发送方法-通过将输入保存到一些数据结构,将用于未来的状态验证,如一些对象的数组,然后它测试你将检查sentemail包含预期的电子邮件。这是状态验证:
断言。emailSender.SentEmails.Count AreEqual (1)
从我的阅读中,我了解到行为验证通常被称为mock。
状态验证通常被称为存根或假。
Stub和fake是对象,因为它们可以根据输入参数改变响应。它们之间的主要区别是Fake比存根更接近真实世界的实现。存根基本上包含对预期请求的硬编码响应。让我们看一个例子:
public class MyUnitTest {
@Test
public void testConcatenate() {
StubDependency stubDependency = new StubDependency();
int result = stubDependency.toNumber("one", "two");
assertEquals("onetwo", result);
}
}
public class StubDependency() {
public int toNumber(string param) {
if (param == “one”) {
return 1;
}
if (param == “two”) {
return 2;
}
}
}
A mock is a step up from fakes and stubs. Mocks provide the same functionality as stubs but are more complex. They can have rules defined for them that dictate in what order methods on their API must be called. Most mocks can track how many times a method was called and can react based on that information. Mocks generally know the context of each call and can react differently in different situations. Because of this, mocks require some knowledge of the class they are mocking. a stub generally cannot track how many times a method was called or in what order a sequence of methods was called. A mock looks like:
public class MockADependency {
private int ShouldCallTwice;
private boolean ShouldCallAtEnd;
private boolean ShouldCallFirst;
public int StringToInteger(String s) {
if (s == "abc") {
return 1;
}
if (s == "xyz") {
return 2;
}
return 0;
}
public void ShouldCallFirst() {
if ((ShouldCallTwice > 0) || ShouldCallAtEnd)
throw new AssertionException("ShouldCallFirst not first thod called");
ShouldCallFirst = true;
}
public int ShouldCallTwice(string s) {
if (!ShouldCallFirst)
throw new AssertionException("ShouldCallTwice called before ShouldCallFirst");
if (ShouldCallAtEnd)
throw new AssertionException("ShouldCallTwice called after ShouldCallAtEnd");
if (ShouldCallTwice >= 2)
throw new AssertionException("ShouldCallTwice called more than twice");
ShouldCallTwice++;
return StringToInteger(s);
}
public void ShouldCallAtEnd() {
if (!ShouldCallFirst)
throw new AssertionException("ShouldCallAtEnd called before ShouldCallFirst");
if (ShouldCallTwice != 2) throw new AssertionException("ShouldCallTwice not called twice");
ShouldCallAtEnd = true;
}
}