如何编写模拟对象?

时间:2009-01-15 16:39:49

标签: unit-testing mocking

我的第一个编程工作向我介绍了单元测试和模拟对象的概念,但总有一些问题。

假设我们正在编写银行应用程序,并且需要模拟BankAccount对象:


    // boilerplate code
    public interface IBankAccount {
        void Deposit(int amount);
        void Withdrawal(int amount);
        int getBalance();
        int getAccountNumber();
    }

    public interface IBankAccountFactory {
        IBankAccount getAccount(int accountNumber);
    }

    public class ProductionBankAccountFactory implements IBankAccountFactory { 
        public IBankAccount getAccount(int accountNumber) {
            return new RealBankAccount(accountNumber);
        }
    }

    public class MockBankAccountFactory implements IBankAccountFactory {
        public IBankAccount getAccount(int accountNumber) {
            return new MockBankAccount(accountNumber);
        }
    }

    public static class BankAccountFactory {
        // ewww, singletons!
        public static IBankAccountFactory Instance;
    }

    // finally, my actual business objects
    public class MockBankAccount implements IBankAccount {
        public MockBankAccount(int accountNumber) { ... }
        // interface implementation
    }

    public class RealBankAccount implements IBankAccount {
        public RealBankAccount(int accountNumber) { ... }
        // interface implementation
    }

每个班级都有一个目的:

  • 存在工厂和工厂接口,用于将构造函数包装到我们的模拟和真实对象中。
  • 静态BankAccountFactory类允许我们在生产应用程序或测试开始时分别为BankAccountFactory.Instance分配一个IRealBankAccountFactory或MockBankAccountFactory实例。
  • 一切设置正确后,任何班级都可以通过调用来获取IBankAccount实例:

    BankAccountFactory.Instance.getAccount(accountNum);

这样可行,但它会产生很多的样板代码。我不应该为每个我想要模拟的类编写5个新类。我相信有一种更简单的方法,所以我不得不问SO社区:

是否有更好或更优先的方式来编写模拟对象?

[编辑添加:] 我很欣赏模拟和DI框架的链接,但是现在我正在研究500 KLOC应用程序,并且至少有60%的代码包含上面样式的样板模拟类。

我只是想减少代码库的大小而不重写Yet-Another-Framework™的大块代码,所以它有助于我更多地看到手工编写的模拟类。 :)

3 个答案:

答案 0 :(得分:8)

更好的方法是让别人写下来。这里的一些选择是:

Moq - http://code.google.com/p/moq/

犀牛嘲笑 - http://ayende.com/projects/rhino-mocks.aspx

答案 1 :(得分:1)

我想我的第一个问题是为什么你需要使用工厂模式来包装对象的结构;特别是你的Mock对象。由于套件中的每个单元测试应完全独立于任何其他单元测试运行,因此您似乎可以直接在单元测试类的setUp方法中实例化MockBankAccount,甚至可以在测试本身中实例化。如果我在上面的情况,我会写这样的东西:

public interface IBankAccount {
    void Deposit(int amount);
    void Withdrawal(int amount);
    int getBalance();
    int getAccountNumber();
}

public class MockBankAccountFactory implements IBankAccountFactory {
    public IBankAccount getAccount(int accountNumber) {
        return new MockBankAccount(accountNumber);
    }
}

public class BankAccountUnitTest extends TestCase {
    IBankAccount testBankAccount;

    public void setUp() {
        testBankAccount = new MockBankAccount(someAccountNumber);
    }

    // Unit tests here
}

如果您正在使用工厂来对使用 IBankObject的另一个类进行单元测试,那么您应该查看dependency injection以向该类提供模拟对象,而不是被测试的类实例化一个模拟对象。

答案 2 :(得分:0)

有一些Mock库通过允许您在单元测试代码中指定对象及其行为来简化该过程。

一个很好的例子是Moq库(http://code.google.com/p/moq/