C#如何在没有实现的情况下单元测试接口方法

问题描述:

我是单元测试和计算器的新手。C#如何在没有实现的情况下单元测试接口方法

我在下面的接口测试RefreshAmount

public interface IAccountService 
{ 
    double GetAccountAmount(int accountId); 
} 

这里是取决于这个接口的类:

public class AccountObj 
{ 
    private readonly int _Id; 
    private readonly IService _service; 
    public AccountObj(int Id, IService service) 
    { 
     _Id = Id; 
     _service = service; 
    } 
    public double Amount { get; private set; } 
    public void RefreshAmount() 
    { 
     Amount = _service.GetAmount(_Id); 
    } 
} 

我的单元测试的RefreshAmount的行为如何?

RefreshAmount致电IService.GetAmount可能会打电话给后台办公室,但我没有实施。任何建议的路上将不胜感激。 (我看了一下起订量和依赖注入,但我安静新的单元测试)

+0

测试接口本身感觉没有意义,因为没有要测试的实现。 1.编写一个实现并用单元测试进行测试 2.正如你所提到的,你可以使用Moq来为你的接口提供一个测试实现 –

+0

@ J.Tuc我不认为他想测试这个接口, AccountObj'使用该接口。 –

+0

您的'AccountObj'看起来像一个实体。您应该[不要将依赖关系注入到实体的构造函数中](https://*.com/a/28767898/264697)。 – Steven

使用起订量,这里是有意见

[TestClass] 
public class AccountObjUnitTests { 
    [TestMethod] 
    public void AccountObj_Given_Id_RefreshAmount_Should_Return_Expected_Amount() { 

     //Arrange 
     //creating expected values for test 
     var expectedId = 1; 
     var expectedAmount = 100D; 
     //mock implementation of service using Moq 
     var serviceMock = new Mock<IService>(); 
     //Setup expected behavior 
     serviceMock 
      .Setup(m => m.GetAmount(expectedId))//the expected method called with provided Id 
      .Returns(expectedAmount)//If called as expected what result to return 
      .Verifiable();//expected service behavior can be verified 

     //the system under test 
     var sut = new AccountObj(expectedId, serviceMock.Object); 

     //Act 
     //exercise method under test 
     sut.RefreshAmount(); 


     //Assert 

     //verify that expectations have been met 
     serviceMock.Verify(); //verify that mocked service behaved as expected 
     Assert.AreEqual(expectedAmount, sut.Amount); 
    } 

    //Samples class and interface to explain example 
    public class AccountObj { 
     private readonly int _Id; 
     private readonly IService _service; 
     public AccountObj(int Id, IService service) { 
      _Id = Id; 
      _service = service; 
     } 
     public double Amount { get; private set; } 
     public void RefreshAmount() { 
      Amount = _service.GetAmount(_Id); 
     } 
    } 

    public interface IService { 
     double GetAmount(int accountId); 
    } 
} 

一个小例子测试这里是相同的测试

[TestMethod] 
public void AccountInfo_RefreshAmount_Given_Id_Should_Return_Expected_Amount() { 
    //Arrange 
    //creating expected values for test 
    var expectedId = 1; 
    var expectedAmount = 100D; 
    //mock implementation of service using Moq with expected behavior 
    var serviceMock = Mock.Of<IService>(m => m.GetAmount(expectedId) == expectedAmount); 
    //the system under test 
    var sut = new AccountObj(expectedId, serviceMock); 

    //Act 
    sut.RefreshAmount();//exercise method under test 

    //Assert 
    Assert.AreEqual(expectedAmount, sut.Amount);//verify that expectations have been met 
} 

我不知道起订量框架。我们使用MSTest和Microsoft Fakes这可以为您提供存根。

然而,天真的直接的方式可以实现在测试类

public class MyTestImplementation : IAccountService 
{ 
    public bool HasBeenCalled { get; private set; } 
    public int ProvidedId { get; private set; } 
    public double Amoung { get; set; } 

    public double GetAccountAmount(int accountId) 
    { 
     HasBeenCalled = true; 
     ProvidedId = accountId; 
    } 
} 

的接口和测试方法:

[TestMethod] // or whatever attribute your test framework uses 
public void TestInterface() 
{ 
    const double EXPECTEDAMOUNT = 341; 
    const int EXPECTEDID = 42; 
    MyTestImplementation testImpl = new MyTestImplementation(); 
    testImpl.Amount = EXPECTEDAMOUNT; 

    var sut = new AccountObj(EXPECTEDID, testImpl); 

    sut.RefreshAmount(); 

    // use your assertion methods here 
    Assert.IsTrue(testImpl.HasBeenCalled); 
    Assert.AreEqual(EXPECTEDID, testImpl.ProvidedID); 
    Assert.AreEqual(EXPECTEDAMOUNT, sut.Amount); 

} 

如果你只是想检查由此产生的Amount是正确的,你可以忽略其他属性。通常情况下,你不想检查RefreshAmount()如何做它应该做的,但只有当产生Amount是正确的。这种形式的东西

+0

感谢您的快速回答。因此,如果我从您的代码中了解,测试即将检查该方法是否已被有效调用,以及提供的Id是否正确。对?这不是检查返回值是否正确。我在这里得到这个概念吗? –

+0

@AxelBetton哦,对,补充说,验证。我有时候仍然感到困惑 - 我应该真的测试一下。 –

单元测试是关系到你的类的好作文。在这种情况下是。您可以将“服务”传递给您的班级,并为您完成工作。

什么,你需要做的是让“testServiceClass”,将工作只是为了测试,但不会做别的。

注:下面代码是丢失了所有的“测试” atributes向大家介绍的代码

namespace MyTests 
{ 
    public class AccountObjTest 
    { 
     public void Test1() 
     { 
      int testVal = 10; 

      AccountObj obj = new AccountObj(testVal, new AccountObjTestService()); 
      obj.RefreshAmount();   
      Assert.AreEquals(obj.Amount, testVal); 
     } 
    } 

    internal class AccountObjTestService : IAccountService 
    { 
     public override double GetAmount(int accountId) 
     { 
      return accountId; 
     } 
    } 
} 

这是重要的,你的是,类本身被选中(单元测试),而不是整个实施几个类(INTEGRATION-test)。

+0

没错。如果没有注入的实现,则无法测试接口。你需要某种实现,或者模拟接口和方法。 – Sietse

更简化的版本,我强烈建议你使用起订量为你正在努力实现。它将允许你“伪装”你正在传递给你想要测试的类的任何接口的实现。这样你一次只能测试一个类的行为。从长远来看,这将使单元测试变得更加容易。

创建一个测试类似乎是一个简单的选择,但是,如果您要处理大量场景,那么测试类将不得不增长并且变得更复杂以迎合所有场景,并且在某个点由于其复杂性,您也必须对其进行测试。

试着学习如何使用moq,它将在长期内得到回报。您将能够验证传递给模拟对象的数据,控制模拟行为返回的内容,测试是否调用了模拟方法以及调用了多少次。 Moq非常有用。

看一看https://github.com/Moq/moq4/wiki/Quickstart它解释了如何使用起订量得很好begginers

我认为,如果你在你的代码中的一些输入错误,因为你的界面被称为IAccountService而你的服务实现IService。你的班级名为AccountObj,而你的构造函数名为AccountInfo

因此,让我们假设您的服务预计实施IAccountService,并且您的班级预计名为AccountInfo

在编写单元测试以测试AccountInfo.RefreshAmount时,您必须了解此功能的要求;你必须确切知道这个函数应该做什么。

看代码似乎的RefreshAmount的要求是:

无论实现IAccountService和任何标识用于构建AccountInfo类的对象对象,调用RefreshAmount的后置条件是财产Amount返回与IAccountService.GetAccountAmount(Id)相同的值。

或者更正式的:

  • 对于任何标识
  • 为应调用RefreshAmount(后实现IAccountService
  • 正在使用Id和IAccountService创建的对象的任何类),返回一个值对于财产金额,等于由IAccountService.GetAccountAmount(Id)返回的值。

因为要求说它应该适用于所有的ID和所有的AccountServices,所以你不能用所有的ID来测试你的课程。在编写单元测试的这种情况下,你必须考虑可能发生的错误。

在你的例子中,它看起来没有可能的错误,但是对于将来的版本,你可能会想到该函数会用不正确的ID调用服务,或者调用不正确的服务,或者忘记保存返回值财产金额或该财产金额不会返回正确的值。

您的类的要求指定它应该适用于每个Id和每个IAcocuntService。因此,您可以*地向要测试的测试对象的构造函数提供任何Id和AccountService,这些可能会检测到四个未来错误中的任何一个。

幸运的是一个简单的AccountInfo类就足够

class AccountService : IAccountService 
{ 
    public double GetAccountAmount(int accountId) 
    { 
     return 137 * accountId - 472; 
    } 
} 

以下单元测试的测试,该量是由提供的实现IAccountService的几个IDS返回

空隙TestAccountObj_RefreshAmount() { 量const int Id = 438; IAccountService accountService = new AccountService(); var testObject = new AccountInfo(Id,accountService);

testObject.RefreshAmount(); 
    double amount = testObject.Amount; 
    double expectedAmount = accountService.GetAccountAmount(Id); 
    Assert.AreEqual(expectedAmount, amount); 

}

此测试将测试所有四个提到可能的错误。它不会找到的唯一的错误是,如果这个不正确的服务将返回完全相同的奇怪计算的数字,它会调用不正确的服务。这就是为什么我把这种奇怪的计算放在服务中,任何不正确调用的服务都不可能返回相同的错误。特别是如果你用不同的Ids测试使用各种TestObject