如何用硬编码对象模拟方法?


11

我正在研究具有多层的应用程序。数据访问层从数据源检索和保存数据,业务逻辑处理数据,用户界面在屏幕上显示数据。

我还对业务逻辑层进行了单元测试。唯一的要求是测试业务层逻辑流。因此,我使用Moq框架来模拟数据访问层,并使用MS Unit对业务逻辑层进行单元测试。

我正在使用接口编程来使设计尽可能地分离,以便可以进行单元测试。业务层通过接口调用数据访问层。

当我尝试测试一种业务逻辑方法时,我遇到了一个问题。该方法完成了一些工作并创建了一个对象,并将其传递给数据访问层。当我尝试模拟该数据访问层方法时,则无法成功模拟。

在这里,我试图创建一个演示代码来显示我的问题。

模型:

public class Employee
{
    public string Name { get; set; }
}

数据访问层:

public interface IDal
{
    string GetMessage(Employee emp);
}

public class Dal : IDal
{
    public string GetMessage(Employee emp)
    {
        // Doing some data source access work...

        return string.Format("Hello {0}", emp.Name);
    }
}

业务逻辑层:

public interface IBll
{
    string GetMessage();
}

public class Bll : IBll
{
    private readonly IDal _dal;

    public Bll(IDal dal)
    {
        _dal = dal;
    }

    public string GetMessage()
    {
        // Object creating inside business logic method.
        Employee emp = new Employee(); 

        string msg = _dal.GetMessage(emp);
        return msg;
    }
}

单元测试:

[TestMethod]
    public void Is_GetMessage_Return_Proper_Result()
    {
        // Arrange.
        Employee emp = new Employee; // New object.

        Mock<IDal> mockDal = new Mock<IDal>();
        mockDal.Setup(d => d.GetMessage(emp)).Returns("Hello " + emp.Name);

        IBll bll = new Bll(mockDal.Object);

        // Act.

        // This will create another employee object inside the 
        // business logic method, which is different from the 
        // object which I have sent at the time of mocking.
        string msg = bll.GetMessage(); 

        // Assert.
        Assert.AreEqual("Hello arnab", msg);
    }

在模拟时的单元测试用例中,我正在发送一个Employee对象,但是当调用业务逻辑方法时,它正在该方法内部创建另一个Employee对象。这就是为什么我不能嘲笑对象。

在那种情况下,如何设计才能解决问题?


通常的诀窍是将对象包装在一个接口中,并使它的所有使用者使用该接口,然后您只需模拟该接口,或者可以使该方法成为虚拟方法,然后moq可以模拟不具有该接口的方法。但是,在这种情况下,不确定鼻孔虫或其他。
Jimmy Hoffa 2013年

Answers:


12

与其Employee直接使用创建对象new,您的类Bll可以EmployeeFactory为此使用一个类,并带有一个方法createInstance,该方法通过构造函数注入:

 class EmployeeFactory : IEmployeeFactory
 {
       public Employee createInstance(){return new Employee();}
 }

构造函数应该通过接口获取工厂对象IEmployeeFactory,因此您可以用模拟工厂轻松地替换“真实”工厂。

public class Bll : IBll
{
    private readonly IDal _dal;
    private readonly IEmployeeFactory _employeeFactory;

    public Bll(IDal dal, IEmployeeFactory employeeFactory)
    {
        _dal = dal;
        _employeeFactory=employeeFactory;
    }

    public string GetMessage()
    {
        // Object creating inside business logic method
        // *** using a factory ***
        Employee emp = _employeeFactory.createObject(); 
        // ...
    }
    //...
}

模拟工厂可以为测试提供测试所需的任何类型的Employee对象(例如,createInstance始终可以返回相同的对象):

 class MockEmployeeFactory : IEmployeeFactory
 {
       private Employee _emp;

       public MockEmployeeFactory()
       {
          _emp = new Employee();
          // add any kind of special initializing here for testing purposes
       }

       public Employee createInstance()
       {
          // just for testing, return always the same object
          return _emp;
       }
 }

现在,在您的测试中使用此模拟程序即可解决问题。


您能给我一个代码示例,以便我可视化您的理论吗?
DeveloperArnab

@DeveloperArnab:看看我的编辑。
布朗

非常有帮助...
DeveloperArnab

4

我会将其视为要测试的单个单元。

只要您控制从中Employee创建对象的所有输入,就不必在测试对象中创建对象这一事实。如果参数的内容符合期望,则只需要一个模拟方法即可返回期望的结果。

显然,这意味着您需要为模拟方法提供自定义逻辑。高级逻辑通常不能仅使用“ for x return y”这种模拟进行测试。

事实上,你应该让它在测试中返回不同的对象比它会在生产,因为如果你这样做,你就不会被测试应该创建它的代码。但是该代码是生产代码不可或缺的一部分,因此也应包含在测试用例中。


是的,我不必理会数据访问层的输入,我只想模拟该对象并返回一个硬编码的数据,以便可以测试业务逻辑。但是问题是因为有两个不同的Employee对象,所以无法模拟数据访问层方法。
DeveloperArnab

@DeveloperArnab:对象将​​有所不同,但是它们具有已知的内容。因此,您要做的就是使模拟执行自定义比较,而不是对象标识。
2013年

@DeveloperArnab:如果Employee在测试中注入其他对象,则不会测试通常创建该对象的代码。因此,您不应该更改它。
2013年

0

这是某些测试工具的失败,您必须始终使用接口,并且必须以允许您将基于接口的对象换成另一个对象的方式创建所有内容。

但是,有更好的工具-使用Microsoft Fakes(称为Moles),它可以让您交换任何对象,甚至静态对象和全局对象。它采用了一种更底层的方法来替换对象,因此您不必在所有地方都使用接口,同时仍保持编写习惯的方式。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.