【问题标题】:Refactoring to test重构测试
【发布时间】:2011-02-18 11:13:43
【问题描述】:

我有一段代码大致相当于下面的代码。

public class ConcreteThread extends OtherThread { private DAOfirst firstDAO; private DAOsecond secondDAO; private TransformService transformService; private NetworkService networkService; public ConcreteThread(DAOfirst first, DAOsecond second, TransformService service1, NetworkService service2) { firstDAO = first; secondDAO = second; transformService = service1; networkService = service2; } public Future go() { Results r1 = firstDAO.getResults(); MyCallable c1 = new MyCallable(r1); return super.getThreadPool().submit(c1); } private class MyCallable implements Callable { private Results result; private Long count; private MyCallable(Results r) { this.result = r; this.count = new Long(0); } public Long call() { Singleton transactions = Singleton.getInstance(); try { transactions.begin(); while(result != null) { Transformed t = transformService.transform(r1); networkService.sendSomewhere(t); count = count += result.size(); secondDao.persist(result); result = firstDao.getNext(result); } } catch (Exception e) { e.printStackTrace(); } finally { transactions.end(); } } }

这些类(内部或外部)都没有单元测试,结果发现内部类MyCallable 有一个错误。在我上面给你的代码的简化版本中,这个错误不存在。

因此,假设您决定修复错误,并为MyCallable 实施一些单元测试。我的问题是这个;您将如何为MyCallable 内部类编写单元测试?

我自己的解决方案是首先重构MyCallableConcreteThreadMyCallable 在其自己的文件中成为公共类,ConcreteThread 现在将 DAO、Services 和 Singleton 作为构造函数参数传递给 MyCallable,而不是依赖于内部类对其私有变量的访问。

然后我在单元测试中大量使用 EasyMock 来模拟这些依赖项并验证它们是否以我预期的方式被调用。

所有这一切的结果是MyCallable 的代码比原来要大一些。由于它不再能够访问ConcreteThread 中的私有变量,ConcreteThread 必须将它们作为参数传入构造函数,而MyCallable 将它们设置为私有变量。

您认为这是错误的方法吗?也许通过执行这种重构,我破坏了封装并在代码库中添加了不必要的样板?你会在测试中使用反射吗?

【问题讨论】:

    标签: java unit-testing reflection encapsulation easymock


    【解决方案1】:

    所有这一切的结果是 MyCallable 的代码比原来要大一些。由于它不再可以访问 ConcreteThread 中的私有变量,因此 ConcreteThread 必须将它们作为参数传入构造函数中,而 MyCallable 将它们设置为私有变量。

    这是一个很好的结果,MyCallable 不再依赖于 ConcreteThread 的变化。

    我认为问题和答案相当主观,但我认为您在重构中遵循了SOLID 原则(这是一件好事)。

    如果可以的话,让 MyCallable 包受到保护,而不是公开:)

    【讨论】:

    • +1 完全同意。关于封装,只要您将 MyCallable 包保持在本地(正如 Augusto 建议的那样),您就不会破坏封装,您只是放松它以帮助测试,这是一种很常见的做法。您可能希望在某处的评论中说明这一点。至于样板,它真的不会增加代码的复杂性,所以不要太担心。稍微多一点的样板代码比有缺陷、未经测试的代码要好。
    • 而不是“放松”封装来帮助测试,我会使用反射 API;这是强制执行测试异常的方法,因为“放松”仅对反射实例有效。现在这是更多样板文件,但是有 dp4j.com 注入了该样板文件。就像你们俩的样板代码更少(更少的代码+更易于阅读)-> 更少的错误代码。谈到单例,请考虑@Singleton。
    【解决方案2】:

    在我看来,内部类是外部类的实现细节。

    所以我提出这个问题,你能通过为 ConcreteThread.Go() 编写一个失败的单元测试来演示这个错误吗?对内部类进行更改后应该有什么不同 - 外部可见的更改是什么?一旦你弄清楚了——你就可以上路了。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-11-15
      • 2011-04-16
      • 2012-09-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多