【问题标题】:Unit testing (java): Will I have to write public methods to expose data to unit tests?单元测试(java):我是否必须编写公共方法来将数据公开给单元测试?
【发布时间】:2016-09-08 13:30:40
【问题描述】:

我见过this,但它并没有很好的答案。有时您需要测试一个类的某些行为,但要真正断言您具有预期的行为,您需要检查它的一些私有数据。

一个例子:我正在创建一个类,它将返回从文件中读取的随机单词。所以我设计了一个这样的类:

public class WordsDatabase {
    private List<String> wordsList = new ArrayList<String>();
    
    public WordsDatabase() {
       fillWordsListFromFile();
    }

    private void fillWordsListFromFile() {...}

    public String getRandomWord() {...}
}

我不想公开 wordsList,但现在我应该如何单元测试 getRandomWord() 是否真的从我的字典文本文件中获取了一个随机单词?

我只能测试它是否返回一个单词,但我不知道该单词是否是从文件中随机挑选出来的。

为了测试我是否可以对均匀分布执行卡方检验,但我必须至少知道 wordsList.size(),以某种方式公开它。

也许我只是愿意进行太深的测试......

编辑:

感谢您的回答,得到了提示。当我的课程难以测试时,可能是因为它的设计有问题。

【问题讨论】:

  • 这属于programmers.stackexchange.com - 一个用于编程理论/最佳实践的 stackexchange 站点。 Stack Overflow(此站点)用于特定的编程/代码问题。您的问题是关于单元测试理论/最佳实践/方法。
  • @mmcrae:这不是题外话。
  • 那里已经提出并回答了一个密切相关的问题:programmers.stackexchange.com/q/199090/25768
  • 不要在两个网站上发帖。选择一个。
  • a) 事情不需要完全public: stackoverflow.com/a/6913490/995891 b) 你的测试可以测试一个小的测试列表,例如10 件事,调用常规方法 1000 次,检查你得到的每一件事是否大致均匀。这目前不起作用,因为您的类无法正确建模它的依赖关系并对其进行硬编码(也就是使列表可配置)

标签: java unit-testing junit


【解决方案1】:

“如果 getRandomWord() 真的从我的字典文本文件中得到一个随机单词,我应该如何进行单元测试?”

这听起来像是模拟和dependency injection 的完美用例。

  1. 更新您的类,以便在构建时传入此字典
  2. 在您的单元测试中,传入内容非常有限的不同模拟字典/在调用时将返回特定数据
  3. 此次更新后,您对getRandomWord 的单元测试只是验证是否使用了来自模拟 wordList 的预期数据。

如果您使用的是像 spring 这样的框架,它是为依赖注入而设计的。 This other answer 有一些关于使用弹簧解决这个问题的好建议。

【讨论】:

  • 这是一个有趣的解决方案,因此该类不会绑定到单个字典。实际上这是更好的设计,因为它之前的样子并不是一个真正的类,而是一个对象。
  • 将课程分成两部分,一个用于阅读,另一个用于从列表中获取随机单词不是一个好主意吗?
  • @LukeW 有很多不同的方法可以完成重构以使用依赖注入——这是一种可能的想法——另一种可能是将“RandomNumberProvider”提取为另一个依赖项。对此的“正确”细分完全取决于周围的环境——即:这个特定的类如何适应?还有什么可以重复使用的?什么可以在其他地方重复使用?等等
【解决方案2】:

您应该只测试公共方法以确保您的类是否按您喜欢的方式工作。

测试对象的内部状态不是一个好习惯,因为对象的内部表示可以改变,但方法的行为可以保持不变。

因此,您无需更改变量/方法的可见性即可对类进行单元测试,也不应使用反射进行测试(有时它被用作解决此类问题的提示)。


注意:如果你需要知道 wordsList 的大小。您需要检查 wordsList 的填充方式。从您的代码看来,它是从文件中填充的。因此,您定义要在测试中使用的文件。知道了这个文件的内容,你就不需要检查 wordsList 大小的内部值了。

【讨论】:

  • 谢谢,我虽然在测试代码中使用相同的文件,但是我害怕过度复杂化我的单元测试,此外我必须公开一个静态 TEXTFILEPATH 以确保两者都是一样的。
【解决方案3】:

你绝对不应该在 JUnit 测试中使用反射,除非你真的别无选择。如果要执行涉及私有变量的测试,则应使用注入或自动装配。

考虑使用这些类型的注释:

  1. @Inject
  2. @Autowire
  3. @Bean
  4. @Produce
  5. @值

在您的情况下,对于您的列表,@Inject 或 @Autowire 是最适合注入变量的注释。请记住,您需要定义一个应用程序上下文。为此使用 Spring 或仅使用 J2EE 或任何其他支持 CDI 的出色平台。

更重要的是,如果你有私有方法是有原因的,那么就保持这种状态。我认为通常只为了满足单元测试而公开方法是个坏主意。

【讨论】:

    【解决方案4】:

    更改您的设计可以让您更轻松地测试您的课程。例如,您可以将服务/数据访问对象注入您的 WordsDatabase,它实际上会从某些资源中为您检索单词并且具有可移动/可交换的好处:

    public class WordsDatabase {
        private List<String> wordsList = new ArrayList<String>();
    
        public WordsDatabase(WordService wordService) {
           wordsList.addAll(wordService.getWords());
        }
    
        public String getRandomWord() {
            // Interact with wordsList or wordService directly.
        }
    }
    

    一个示例服务接口:

    public interface WordService {
        List<String> getWords();
    }
    

    现在,在您的测试中,您可以mock 一个WordService 的实例,它返回适当的字符串List,或者提供一些其他受控实现。

    如果您控制类的依赖关系,这种问题(很大程度上)就会消失。查看dependency injection 的示例以了解更多信息。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-07-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-09-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多