65.9K
CodeProject 正在变化。 阅读更多。
Home

在 Rhino Mocks 3.5 中使用 VB.Net 的新 AAA 语法

emptyStarIconemptyStarIconemptyStarIconemptyStarIconemptyStarIcon

0/5 (0投票)

2008年10月7日

CPOL

4分钟阅读

viewsIcon

25977

downloadIcon

93

一篇关于如何使用 VB.Net 的新 AAA(Arrange, Act and Assert)语法的文章。以及新旧语法之间的区别。

引言

自 2008 年 10 月 3 日起,Ayende 发布了新版本的 Rhino Mocks。

Rhino Mocks 是“一个用于 .Net 平台的动态 Mock 对象框架。它的目的是通过允许开发人员创建自定义对象的 Mock 实现并使用单元测试来验证交互,从而简化测试。”

此版本现已支持 AAA(Arrange, Act and Assert) 语法。

Rhino Mocks 3.5 中最重要的功能是 AAA 语法。Arrange, Act, Assert 是一种经典的设置测试方式。首先,您安排好状态,然后执行要测试的代码,最后,您断言预期的状态更改已经发生。

这使得它更容易阅读和使用。我将尝试解释如何使用它,并使用 VB.Net 展示新旧语法之间的区别。本文并非讨论为什么以及何时应该使用 Mocks。也不是关于最佳实践的文章。例如,Repository 部分保持得比较轻量,仅仅是为了解释概念。

我将只写一个测试,看看服务在被调用时是否返回了正确的内容。为此,它需要使用 Repository 实现,但由于我们不希望测试访问数据库,所以我们将对其进行 Mock。

您可以在 LessThanDot 上找到我以及其他一些聪明的人。

必备组件

  • 我在此示例中使用了 Rhino Mocks 3.5,可以在 此处 找到。
  • 以及 NUnit 2.4.6.0,可以在 此处 找到。

设置

对于这个例子,我将使用一个 Service 和一个 Repository。Service 调用 Repository 来完成繁重的工作。两者都有一个接口,这通常是 Mock 对象的更简单方式。

首先,我们创建 IService 和 IRepository 接口

Namespace Service
    Public Interface IService
        Function FindAllProducts() As IList(Of Model.Product)
    End Interface
End Namespace

Namespace Repository
    Public Interface IRepository(Of T)
        Function FindAll() As IList(Of T)
    End Interface
End Namespace

现在,两个接口都需要一个实现。没有什么花哨的,只是一个简单的实现。Repository 代码仅作为示例。它甚至不会工作。而且它不需要工作,因为我们要 Mock 它。

首先是 Service。它只是调用 Repository。它还接受一个 IRepository(Of Model.Product) 作为参数,以便我们可以向其中注入一个实现。

Namespace Service
    Public Class Service
        Implements IService

        Private _Repository As Repository.IRepository(Of Model.Product)

        Public Sub New(ByVal Repository As Repository.IRepository(Of Model.Product))
            _Repository = Repository
        End Sub

        Public Function FindAllProducts() As IList(Of Model.Product) _
                                 Implements IService.FindAllProducts
            Return _Repository.FindAll
        End Function
    End Class
End Namespace

现在是 IRepository 的一个实现,我们不会在测试中使用它。

Imports System.Data.SqlClient

Namespace Repository
    Public Class ProductRepository
        Implements IRepository(Of Model.Product)

        ''' 
        ''' Here is where you would go to the database
        ''' 
        ''' A list of Products
        ''' 
        Public Function FindAll() As System.Collections.Generic.IList(Of Model.Product) _
                          Implements IRepository(Of Model.Product).FindAll
            Dim _List As New List(Of Model.Product)
            Dim _con As New SqlConnection
            Dim _com As New SqlCommand
            Dim _reader As SqlDataReader
            _con.ConnectionString = "some connectionstring"
            _con.Open()
            _com.CommandText = "Select Name from product"
            _com.Connection = _con
            _reader = _com.ExecuteReader
            While _reader.Read
                _List.Add(New Model.Product(_reader.GetString(0)))
            End While
            Return _List
        End Function
    End Class
End Namespace

测试

Rhino Mocks 3.4

在上一版本的 Rhino Mocks 中,您使用 Replay 和 Verify 语法。所以我们的测试看起来像这样。

Imports NUnit.Framework
Imports Rhino.Mocks

Namespace MockedTest34
     _
    Public Class TestService

#Region " Private members "
        Private _Mocker As MockRepository
#End Region

#Region " Setup and TearDown "
         _
        Public Sub Setup()
            _Mocker = New MockRepository
        End Sub

         _
        Public Sub TearDown()
            _Mocker.ReplayAll()
            _Mocker.VerifyAll()
        End Sub
#End Region

#Region " Tests "
         _
        Public Sub Test_If_Service_Returns_A_List_Of_Products()
            Dim _Repository As Repository.IRepository(Of Model.Product) = _
                        _Mocker.DynamicMock(Of Repository.IRepository(Of Model.Product))()
            Dim _Service As Service.IService = New Service.Service(_Repository)
            Rhino.Mocks.Expect.Call(_Repository.FindAll).IgnoreArguments.Return(New List(Of Model.Product))
            _Mocker.ReplayAll()
            Assert.IsNotNull(_Service.FindAllProducts)
        End Sub
#End Region

    End Class
End Namespace

您在此处看到我们有一个 MockRepository 的实例。该 MockRepository 将使用 DynamicMock 方法为我们创建一个 Mock。在这种情况下,我们 Mock 了 Repository。然后我们创建 Service,并将 Mocked Repository 传递给它。之后,我们设置了期望。我们期望 Repository 上的 Findall 方法会被调用。然后我们必须调用 MockRepository 对象上的 ReplayAll 方法。最后,我们断言服务是否返回了什么,我们并不真正关心那是什么,但由于我们的函数必须返回一个 Ilist(Of Model.Product),所以我们不必真正检查它。

Rhino Mocks 3.5

在新版本中,旧代码仍然有效 ;-)。但我们可以(也应该)改用新的 AAA 语法。正如您将在代码示例中看到的,它使用起来更简单一些。

Imports Nunit.FrameWork
Imports Rhino.Mocks

Namespace MockedTest35
    ''' 
    ''' A TestClass
    ''' 
    ''' 
     _
Public Class TestService

#Region " Tests "

        ''' 
        ''' A Test
        ''' 
        ''' 
         _
        Public Sub Test_If_Service_Returns_A_List_Of_Products()
            Dim _Repository As Repository.IRepository(Of Model.Product) = _
                MockRepository.GenerateMock(Of Repository.IRepository(Of Model.Product))()
            Dim _Service As Service.IService = New Service.Service(_Repository)
            _Repository.Stub(Function(e) e.FindAll).IgnoreArguments.Return(New List(Of Model.Product))
            Assert.IsNotNull(_Service.FindAllProducts)
        End Sub

#End Region

    End Class
End Namespace

首先,我们注意到不再需要创建 MockRepository 的实例。我们将使用 MockRepository 的静态/共享方法。要创建 mock,我们使用 MockRepository 的 GenerateMock 方法。然后我们将该 mock 对象传递给一个新的 Service。我们通过一个名为 stub 的扩展方法来设置我们的期望。它使用 lambda 表达式来决定调用哪个方法。我们设置了期望的返回值,并使用 IgnoreArguments,尽管在这种情况下并非真正需要,因为 findall 没有参数。之后,我们调用服务方法,看看它是否返回了什么。

正如您所见,新方法也不再需要 ReplayAll 和 VerifyAll 语法了。

我希望这能充分解释它们之间的区别,让您能够去使用这个出色的产品。

关注点

如果您从 Rhino Mocks 3.4 切换到 Rhino Mocks 3.5,编译器会在您有 Expect.Call 的行上抱怨 Expect 没有这样的方法。这很容易通过在前面加上 Rhino.Mocks 来解决。这是因为 Ayende 还添加了一个同名的方法,编译器无法区分。添加 imports 没有帮助。

历史

2008/10/07 第一版

2008/10/07 添加了 AAA 解释文章的链接。并修正了一些拼写错误。

© . All rights reserved.