常规单元测试和存根–测​​试技术4

我的上一个博客是有关测试代码的方法以及讨论您要做和不需要进行测试的方法的一系列博客中的第三篇。 它基于我使用一种非常常见的模式从数据库中检索地址的简单方案:

…并且我提出了这样的想法:任何不包含任何逻辑的类都不需要进行单元测试。 在其中,我包括了我的数据访问对象DAO,而不是对此类进行集成测试以确保其与数据库协同工作。

今天的博客涵盖编写常规或经典的单元测试,该测试使用存根对象强制实现测试主题隔离。 我们将要测试的代码再次是AddressService

@Component
public class AddressService {

  private static final Logger logger = LoggerFactory.getLogger(AddressService.class);

  private AddressDao addressDao;

  /**
   * Given an id, retrieve an address. Apply phony business rules.
   * 
   * @param id
   *            The id of the address object.
   */
  public Address findAddress(int id) {

    logger.info("In Address Service with id: " + id);
    Address address = addressDao.findAddress(id);

    address = businessMethod(address);

    logger.info("Leaving Address Service with id: " + id);
    return address;
  }

  private Address businessMethod(Address address) {

    logger.info("in business method");

    // Apply the Special Case Pattern (See MartinFowler.com)
    if (isNull(address)) {
      address = Address.INVALID_ADDRESS;
    }

    // Do some jiggery-pokery here....

    return address;
  }

  private boolean isNull(Object obj) {
    return obj == null;
  }

  @Autowired
  @Qualifier("addressDao")
  void setAddressDao(AddressDao addressDao) {
    this.addressDao = addressDao;
  }
}

迈克尔·费瑟(Michael Feather)的书《有效使用旧版代码》指出,在以下情况下,测试不是单元测试:

  1. 它与数据库对话。
  2. 它通过网络进行通信。
  3. 它涉及文件系统。
  4. 您必须对环境做一些特殊的事情(例如编辑配置文件)才能运行它。

为了遵守这些规则,您需要将测试对象与系统的其余部分隔离开来,这就是存根对象的所在。存根对象是注入到您的对象中的对象,用于在测试情况下替换实际对象。 马丁·福勒(Martin Fowler)在他的论文《 莫克斯不是存根》中将存根定义为:

“存根提供对测试过程中进行的呼叫的固定答复,通常通常根本不响应测试中编程的内容。 存根还可以记录有关呼叫的信息,例如电子邮件网关存根,它可以记住“已发送”的消息,或者仅记住“已发送”的消息数量。”

用一个单词来描述存根非常困难,我可以选择虚拟或伪造,但是有些替换对象称为假人或伪造-也由Martin Fowler描述:

  • 虚拟对象会传递,但从未实际使用过。 通常它们仅用于填充参数列表。
  • 伪对象实际上具有有效的实现,但是通常采取一些捷径,这使它们不适合生产(内存数据库是一个很好的例子)。

但是,我看到过其他术语“伪造对象”的定义,例如Roy Osherove在《 The Art Of Unit Testing》一书中将伪造对象定义为:

  • 伪造品是一个通用术语,可用于描述存根或模拟对象……因为两者看起来都像真实对象。

…因此,我和其他许多人一样,倾向于将所有替换对象称为模拟或存根,因为两者之间存在差异,但稍后会更多。

在测试AddressService时 ,我们需要用存根数据访问对象替换实际的数据访问对象,在这种情况下,它看起来像这样:

public class StubAddressDao implements AddressDao {

  private final Address address;

  public StubAddressDao(Address address) {
    this.address = address;
  }

  /**
   * @see com.captaindebug.address.AddressDao#findAddress(int)
   */
  @Override
  public Address findAddress(int id) {
    return address;
  }
}

注意存根代码的简单性。 它应该易于阅读,可维护,并且不包含任何逻辑,并且需要自己进行单元测试。 编写存根代码后,接下来进行单元测试:

public class ClassicAddressServiceWithStubTest {

  private AddressService instance;

  @Before
  public void setUp() throws Exception {
    /* Create the object to test */
    /* Setup data that's used by ALL tests in this class */
    instance = new AddressService();
  }

  /**
   * Test method for
   * {@link com.captaindebug.address.AddressService#findAddress(int)}.
   */
  @Test
  public void testFindAddressWithStub() {

    /* Setup the test data - stuff that's specific to this test */
    Address expectedAddress = new Address(1, "15 My Street", "My Town",
        "POSTCODE", "My Country");
    instance.setAddressDao(new StubAddressDao(expectedAddress));

    /* Run the test */
    Address result = instance.findAddress(1);

    /* Assert the results */
    assertEquals(expectedAddress.getId(), result.getId());
    assertEquals(expectedAddress.getStreet(), result.getStreet());
    assertEquals(expectedAddress.getTown(), result.getTown());
    assertEquals(expectedAddress.getPostCode(), result.getPostCode());
    assertEquals(expectedAddress.getCountry(), result.getCountry());
  }

  @After
  public void tearDown() {
    /*
     * Clear up to ensure all tests in the class are isolated from each
     * other.
     */
  }
}

请注意,在编写单元测试时,我们的目标是清晰。 通常会犯一个错误,就是认为测试代码不如生产代码,从而导致测试代码更加混乱且难以辨认。 单元测试的艺术中的 Roy Osherove提出了这样的想法,即测试代码应比生产代码更具可读性。 明确的测试应遵循以下基本的线性步骤:

  1. 创建被测对象。 在上面的代码中,这是在setUp()方法中完成的,因为我正在对所有(一个)测试使用相同的被测对象。
  2. 设置测试。 这是在测试方法testFindAddressWithStub()中完成的,因为测试中使用的数据特定于该测试。
  3. 运行测试
  4. 撕毁测试。 这样可以确保测试彼此隔离,并且可以按任何顺序运行。

使用简单的存根会产生将AddressService与外界隔离和快速运行测试的两个好处。

这种测试有多脆? 如果您的要求改变了,那么测试和存根就会改变–毕竟不是那么脆弱吗?

作为比较,我的下一个博客使用EasyMock重写了该测试。

参考: 定期的单元测试和存根-来自JCG合作伙伴 Captain Debug博客上的 测试技术4

相关文章 :


翻译自: https://www.javacodegeeks.com/2011/11/regular-unit-tests-and-stubs-testing.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值