我已经看过了,但它并没有真正的答案。有时你需要测试一个类的某些行为,但是为了你真正断言你有预期的行为,你需要检查它的一些私有数据。
一个例子:我正在创建一个类,它将返回从文件中读取的随机单词。所以我设计了一个这样的类:
public class WordsDatabase {
private List wordsList = new ArrayList();
public WordsDatabase() {
fillWordsListFromFile();
}
private void fillWordsListFromFile() {...}
public String getRandomWord() {...}
}
我不想暴露wordsList,但是现在如果getRandomWord()真的从我的字典文本文件中获取一个随机单词,我应该如何进行单元测试?
我可以测试的是它是否返回一个单词,但我不知道该单词是否是从文件中随机选取的。
为了测试我可以执行Chi Square测试以进行均匀分布,但是至少我必须知道wordsList.size(),以某种方式暴露它。
也许我只是愿意进行太深入的测试......
编辑:
谢谢你的答案,得到了提示。当我的课很难测试时,可能是因为它的设计有问题。
它并不完全重复,但这个问题非常接近:stackoverflow.com/questions/34571/&
我已经看过了,但是我真的应该为那些思考而烦恼吗?
这属于programmers.stackexchange.com - 用于编程理论/最佳实践的stackexchange站点。 Stack Overflow(本网站)用于特定的编程/代码问题。您的问题是关于单元测试理论/最佳实践/方法。
@mmcrae:这不是偏离主题的。
我会试试看,stackexchange可能有一个"迁移到"功能...
并且在那里提出并回答了一个密切相关的问题:programmers.stackexchange.com/q/199090/25768
不要在这两个网站上发布。选一个。
如果将测试类放在与正在测试的类相同的包(不是相同的源文件夹)中,则可以使用package-private来允许测试类访问,而不会暴露给世界。
谢谢@ratchetfreak,讨论确实有所帮助。
a)事情不需要完全public:stackoverflow.com/a/6913490/995891 b)您的测试可以测试一个小的测试列表,例如: 10件事,调用常规方法1000次并检查你是否大致均匀地得到了所有东西。这当前不起作用,因为您的类无法正确建模它的依赖关系并硬编码它们(也就是使列表可配置)
谢谢@zapl,这真的是我班上的问题。
"如果getRandomWord()真的从我的字典文本文件中获取一个随机单词,我应该如何进行单元测试?"
这听起来像是模拟和依赖注入的完美用例。
更新您的类,以便在构造时传入此字典
在单元测试中,传入内容非常有限的不同模拟字典/将在调用时返回特定数据
在此更新之后,getRandomWord的单元测试只是验证是否使用了模拟wordList中的预期数据。
如果您使用像spring这样的框架,那么它就是为依赖注入而设计的。另一个答案有一些关于使用弹簧来解决这个问题的好指示。
这是一个有趣的解决方案,因此该类不会绑定到单个字典。 实际上这是更好的设计,因为它不是真正的类,而是几乎是一个对象。
将类拆分为两个,一个用于读取,另一个用于从List 获取随机单词不是一个好主意吗?
@LukeW有很多不同的方法来完成重构,以使用依赖注入 - 这是一个可能的想法 - 另一种可能是提取'RandomNumberProvider'作为另一个依赖。 对此的"正确"细分完全取决于周围环境 - 即:这个特定的类如何适应? 还有哪些可以重复使用? 什么可以在别处重复使用? 等等
您应该只测试公共方法,以确定您的类是否按照您的喜好工作。
测试对象的内部状态不是一个好习惯,因为对象的内部表示可以改变,但方法行为可以保持不变。
因此,您无需更改变量/方法的可见性以对单元进行单元测试,并且您不应使用Reflection进行测试(有时它会用作解决此类问题的提示)。
注意:如果你需要知道wordsList的大小。您需要检查wordsList的填充方式。从你的代码看来它是从一个文件填充。因此,您要定义要在测试中使用的文件。知道了这个文件的内容,你不需要检查wordsList大小的内部值。
谢谢,我虽然在测试代码中使用相同的文件,但后来我害怕过度复杂的单元测试,此外我将不得不暴露静态TEXTFILEPATH以确保两者是相同的。
改变您的设计可以让您更轻松地测试您的课程。例如,您可以向WordsDatabase注入一个服务/数据访问对象,该对象实际上是从某些资源中为您检索单词,并且具有mocakable / swappable的优点:
public class WordsDatabase {
private List wordsList = new ArrayList();
public WordsDatabase(WordService wordService) {
wordsList.addAll(wordService.getWords());
}
public String getRandomWord() {
// Interact with wordsList or wordService directly.
}
}
示例服务接口:
public interface WordService {
List getWords();
}
现在,在您的测试中,您可以模拟WordService的实例,该实例返回相应的List字符串,或者提供其他一些受控实现。
如果你控制你的类的依赖性,这种问题(很大程度上)就会消失。查看依赖注入的示例以获取更多信息。
除非你真的没有选择,否则你绝对不应该使用反射进行JUnit测试。如果要执行涉及私有变量的测试,则应使用注入或自动装配。
考虑使用这些类型的注释:
@注入
@Autowire
@豆
@生产
@值
在您的情况下,对于您的列表,@ Inject或@Autowire是最适合注入变量的注释。请记住,您需要定义应用程序上下文。使用Spring或仅使用J2EE或支持CDI的任何其他优秀平台。
更重要的是,如果你出于某种原因拥有私有方法,那么请保持这种方式。我认为通常将方法公开只是为了满足单元测试是一个坏主意。