1

我是单元测试的新手,并且对如何编写这样的单元测试方法感到困惑:

public Boolean BeepInTime(Interfaces.IDateTime time,TimeSpan beepTime)
{
    Interfaces.IBeep beep= new Beep();
    var h = time.GetTime();
    if (h == beepTime)
    {
       return beep.Beeping();
    }
    else
    {
       return false;
    }
}

public Boolean Beeping()
{
    try
    {
      SystemSounds.Beep.Play();
      return true;
    }
    catch
    {
      return false;
    }
}

测试时BeepInTime,我希望 ( beep.Beeping()) 不运行。我阅读了 stub 并认为在这种情况下我应该使用 stub 但如何做到这一点令人困惑。您能否发送一些有关存根的简单示例的源代码。

4

3 回答 3

4

解决方案是 - 依赖注入。

每次你做或看到... = new ...();(构造)依赖注入时,基本上显式实例化都会引入依赖关系,然后有时为这样的代码编写单元测试变得不简单。

因此,与其显式实例化Beep类,不如注入它。有什么好处——你已经有一个抽象这个类的接口——IBeep这将允许你在编写单元测试时创建和注入一个模拟而不是真正的类实例。

public Boolean BeepInTime(Interfaces.IBeep beeper, TimeSpan beepTime) 
{
    var h = time.GetTime();            
    if (h == beepTime)            
    {                 
        return beep.Beeping();            
    } 

    return false;
}

Ans 只是想象一下这种技术在注入 DB 访问服务或 Web 服务的情况下是如何有帮助的,在编写单元测试时您不需要真正的 DB 或 Web 服务——您只需模拟 DB/Web 服务就可以了。

使用 RhinoMocks 的示例:

var mock = MockRepository.GenerateMock<IBeep>();

// setup Beeping() return value == true
mock.Expect(m => m.Beeping()).Return(true);
var isBeepInTime = BeepInTime(mock, timeSpan)
于 2012-09-07T13:28:26.753 回答
3

有很多不同的方法可以解决这个问题。最简单的方法是将 Beep 的接口传递给类(通过构造函数或方法本身)。

public Boolean BeepInTime(Interfaces.IDateTime time,TimeSpan beepTime, Interfaces.IBeep beep) 
{ 
    var h = time.GetTime(); 
    if (h == beepTime) 
    { 
        return beep.Beeping(); 
    } 
    else 
    { 
        return false; 
    } 
   } 

在您的单元测试中,您创建一个模拟 IBeep 对象并传入模拟。那里也有很多第三方模拟框架,因此您不必创建自己的模拟。我个人使用最小起订量,这是一个很好的。RhinoMocks 也很受欢迎。两者都是免费的。

如果您不想在生产代码中创建 Beep 类,那么您可以这样做...

public Boolean BeepInTime(Interfaces.IDateTime time,TimeSpan beepTime, Interfaces.IBeep beep = null) 
{ 
    if (beep == null)
         beep = new Beep();

    var h = time.GetTime(); 
    if (h == beepTime) 
    { 
        return beep.Beeping(); 
    } 
    else 
    { 
        return false; 
    } 
   } 

注意:我通常不会将接口传递给构造函数并将其分配给字段,而是将其传递给方法,特别是如果从许多方法调用该类时)。还有许多其他方法可以做到这一点,包括使用 NInject。我只是提供这些示例,让您先了解一下它是如何完成的。

我建议在决定使用任何一种方法之前,先在互联网上进行几天的研究,调查这些事情。

于 2012-09-07T13:26:50.790 回答
2

您首先需要BeepBeepInTime方法中删除依赖项,如下所示:

public Boolean BeepInTime(Interfaces.IDateTime time,TimeSpan beepTime, Interfaces.IBeep beep)
       {           
           var h = time.GetTime();
           if (h == beepTime)
           {

               return beep.Beeping();
           }
           else
           {
               return false;
           }

       }

然后,您可以在使用 Moq 之类的东西进行测试时模拟 IBeep impementation:

//Arrange
var mockBeep = new Mock<Interfaces.IBeep>();
mockBeep.Setup(b => b.Beeping())
.Returns(true);

// Act

var result = myClass.BeepInTime(myTime, myBeepTime, mockBeep.Object);

...
于 2012-09-07T13:31:48.330 回答