首先,我是单元测试的初学者。对于我的测试,我想使用NSubstitute,所以我阅读了网站上的教程以及Richard Banks的模拟比较。它们都是针对接口而不是针对类进行测试。声明是“通常这个[替换]类型将是一个接口,但您也可以在紧急的情况下替换类。”
现在我想知道测试接口的目的。以下是NSubstitute网站的示例界面(请注意,我已经在VB.net中转换了C#代码):
Public Interface ICalculator
Function Add(a As Double, b As Double) As Double
Property Mode As String
Event PoweringUp As EventHandler
End Interface
以下是网站上的单元测试(在NUnit-Framework下):
<Test>
Sub ReturnValue_For_Methods()
Dim calculator = Substitute.For(Of ICalculator)()
calculator.Add(1, 2).Returns(3)
Assert.AreEqual(calculator.Add(1, 2), 3)
End Sub
好的,这样可行,单元测试也会成功。但这有什么意义呢?这不会测试任何代码。 添加 -Method可能有任何错误,在测试接口时将无法检测到 - 如下所示:
Public Class Calculator
Implements ICalculator
Public Function Add(a As Double, b As Double) As Double Implements ICalculator.Add
Return 1 / 0
End Function
...
End Class
添加 -Method执行除零,因此单元测试应该失败 - 但是由于对ICalculator接口的测试,测试成功。
你可以帮我理解一下吗?有什么意义呢,不是测试代码而是测试界面?提前致谢 迈克尔
答案 0 :(得分:0)
模拟背后的想法是将我们正在测试的类与其依赖关系隔离开来。因此,我们不会模拟我们正在测试的类,在这种情况下Calculator
,我们在测试使用ICalculator
的类时模拟ICalculator
。
一个小例子是我们想要测试某些东西与数据库的交互方式,但我们不想使用真正的数据库进行一些快速测试。 (请原谅C#。)
[Test]
public void SaveTodoItemToDatabase() {
var substituteDb = Substitute.For<IDatabase>();
var todoScreen = new TodoViewModel(substituteDb);
todoScreen.Item = "Read StackOverflow";
todoScreen.CurrentUser = "Anna";
todoScreen.Save();
substituteDb.Received().SaveTodo("Read StackOverflow", "Anna");
}
这里的想法是我们将TodoViewModel
与保存到数据库的细节分开。我们不想担心配置数据库,获取连接字符串,或者让先前测试运行的数据干扰未来的测试运行。使用真实数据库进行测试非常有价值,但在某些情况下,我们只想测试较小的功能单元。模拟是这样做的一种方式。
对于真实应用,我们将创建一个TodoViewModel
,其实际实现为IDatabase
,并且假设该实现遵循预期的接口合同,那么我们可以合理地期望它会起作用。
希望这有帮助。
更新以回复comment
TodoViewModel
的测试假定IDatabase
的实施有效,因此我们可以专注于该课程&#39;逻辑。这意味着我们可能需要一组单独的测试来实现IDatabase
。假设我们有一个SqlServerDb
实现,那么我们可以进行一些测试(可能针对一个真正的数据库),检查它是否符合它的承诺。在那些测试中,我们不再嘲笑数据库界面,因为这是我们正在测试的内容。
我们可以做的另一件事是进行合同测试&#34;我们可以将其应用于任何IDatabase
实施。例如,我们可以进行测试,对于任何实现,保存项目然后再次加载应该返回相同的项目。然后,我们可以针对所有实现运行这些测试,SqlDb
,InMemoryDb
,FileDb
等。这样我们可以陈述我们对我们正在嘲笑的依赖关系的假设,然后检查实际的实现符合我们的假设。