Unit testing 单元测试没有返回值的类?
关于这个具体问题,我在教程中找不到太多 所以我有一个名为“Job”的类,它有公共的构造函数和一个公共的Run()函数。类中的所有内容都是私有的,并封装在类中。(你可能还记得这里的一篇老帖子,它的回复对我帮助很大) 这个Run()方法做了很多事情——将excel文件作为输入,从中提取数据,向第三方数据供应商发送请求,获取结果并将其放入数据库,并记录作业的开始/结束 此作业类在其运行方法中使用3个独立的接口/类(IConnection将连接到第三方供应商并发送请求,IParser将解析结果,IDataAccess将结果保存到数据库)。现在,我的Run()方法中唯一真正的逻辑是提取excel输入并将其发送到其他类的链中。我创建了3个模拟类,并在作业类ctor上使用DI,一切都很好 除了-我对如何测试Run()方法仍然有点迷茫-因为它是无效的,不会返回任何东西 在这种情况下,我是否应该向Run()方法添加一个返回值,该方法返回从Excel文件中提取的记录数?因为这是该函数中现在执行的唯一逻辑。。这不会在实际代码中处理,但会在单元测试中处理。。。这对我来说似乎有点难闻——但就真正的TDD而言,我是个新手 第二个问题——我是否应该创建一个名为IExcelExtractor的第四个类,它为我提供了这种逻辑?还是这有点像班级爆炸 即使我做了后者,如果Run()函数返回void,并且它的所有工作都是由模拟对象执行的,而模拟对象实际上什么都不做,我该如何测试它呢?我可以理解我的函数是否有一个有意义的返回值。。。但在这种情况下,我是一个很困惑的人Unit testing 单元测试没有返回值的类?,unit-testing,tdd,mocking,Unit Testing,Tdd,Mocking,关于这个具体问题,我在教程中找不到太多 所以我有一个名为“Job”的类,它有公共的构造函数和一个公共的Run()函数。类中的所有内容都是私有的,并封装在类中。(你可能还记得这里的一篇老帖子,它的回复对我帮助很大) 这个Run()方法做了很多事情——将excel文件作为输入,从中提取数据,向第三方数据供应商发送请求,获取结果并将其放入数据库,并记录作业的开始/结束 此作业类在其运行方法中使用3个独立的接口/类(IConnection将连接到第三方供应商并发送请求,IParser将解析结果,IDat
非常感谢您阅读了所有这些内容,如果您做到了这一点。如果您的
run()
方法所做的唯一事情就是调用其他对象,那么您可以测试它,但要验证是否调用了mock。具体如何做到这一点取决于mock包,但通常会找到某种“expect”方法
不要在run()方法中编写跟踪其执行的代码。如果您无法基于与协作者(mock)的交互来验证方法的操作,则表明需要重新考虑这些交互。这样做也会弄乱主线代码,增加维护成本。当您注入模拟时,您会向运行类的构造函数传递一个测试类,您将询问该测试是否通过。例如,如果在构造函数中传递了excel文件,则可以测试IParser模拟是否获得了正确的请求。您可以通过自己的类来实现这一点,并在其中收集结果并测试它收集的内容,或者您也可以通过模拟框架来实现这一点,该框架为您提供了在不构建类的情况下表达此类测试的方法 我看到你用tdd标记了你的问题,但在真正的tdd中,你并没有真正得到这个问题(你得到了,但问得不同),因为你首先构建了测试,它定义了接口,而不是构建类接口,然后思考如何测试这个东西。测试驱动设计的需要。您仍然使用相同的技术(在本例中可能最终使用相同的设计),但问题会有点不同。我问了一个问题 尽管(理论之上的感觉)我确实认为一些方法不需要单元测试,只要它们:
- 不返回任何值
- 不要更改可以检查的类或系统的内部状态
- 不要依赖于模拟以外的任何东西(作为输入或输出)
如果它们的功能(即调用顺序)至关重要,那么您必须验证是否满足内部功能。这意味着您必须验证(使用您的mock)这些方法是否使用了正确的参数和正确的顺序(如果有关系的话)调用。您提到您在您的自包含类中使用了3个类/接口的mock实现 为什么不创建一些已知值以从mock IConnection返回,只需通过mock IParser传递所有这些值,并将它们存储在mock IDataAccess中,然后在测试中检查mock IDataAccess中的结果是否与通过run()方法运行后mock IConnection输入的预期结果相匹配 编辑以添加示例- 应用程序接口/类:
public interface IConnection {
public List<Foo> findFoos();
}
public interface IParser {
public List<Foo> parse(List<Foo> originalFoos);
}
public interface IDataAccess {
public void save(List<Foo> toSave);
}
public class Job implements Runnable {
private IConnection connection;
private IParser parser;
private IDataAccess dataAccess;
public Job(IConnection connection, IParser parser, IDataAccess dataAccess) {
this.connection = connection;
this.parser = parser;
this.dataAccess = dataAccess;
}
public void run() {
List<Foo> allFoos = connection.findFoos();
List<Foo> someFoos = parser.parse(allFoos);
dataAccess.save(someFoos);
}
}
公共接口i连接{
公共列表findFoos();
}
公共接口IParser{
公共列表解析(列表原始oos);
}
公共接口IDataAccess{
公共作废保存(列表保存);
}
公共类作业实现可运行{
专用i连接;
专用IParser解析器;
私有数据访问;
公共作业(IConnection连接、IParser解析器、IDataAccess数据访问){
这个连接=连接;
this.parser=解析器;
this.dataAccess=dataAccess;
}
公开募捐{
List allFoos=connection.findFoos();
列出someFoos=parser.parse(allFoos);
dataAccess.save(someFoos);
}
}
模拟/测试类:
public class MockConnection implements IConnection {
private List<Foo> foos;
public List<Foo> findFoos() {
return foos;
}
public void setFoos(List<Foo> foos) {
this.foos = foos;
}
}
public class MockParser implements IParser {
private int[] keepIndexes = new int[0];
public List<Foo> parse(List<Foo> originalFoos) {
List<Foo> parsedFoos = new ArrayList<Foo>();
for (int i = 0; i < originalFoos.size(); i++) {
for (int j = 0; j < keepIndexes.length; j++) {
if (i == keepIndexes[j]) {
parsedFoos.add(originalFoos.get(i));
}
}
}
return parsedFoos;
}
public void setKeepIndexes(int[] keepIndexes) {
this.keepIndexes = keepIndexes;
}
}
public class MockDataAccess implements IDataAccess {
private List<Foo> saved;
public void save(List<Foo> toSave) {
saved = toSave;
}
public List<Foo> getSaved() {
return saved;
}
}
public class JobTestCase extends TestCase {
public void testJob() {
List<Foo> foos = new ArrayList<Foo>();
foos.add(new Foo(0));
foos.add(new Foo(1));
foos.add(new Foo(2));
MockConnection connection = new MockConnection();
connection.setFoos(foos);
int[] keepIndexes = new int[] {1, 2};
MockParser parser = new MockParser();
parser.setKeepIndexes(keepIndexes);
MockDataAccess dataAccess = new MockDataAccess();
Job job = new Job(connection, parser, dataAccess);
job.run();
List<Foo> savedFoos = dataAccess.getSaved();
assertTrue(savedFoos.length == 2);
assertTrue(savedFoos.contains(foos.get(1)));
assertTrue(savedFoos.contains(foos.get(2)));
assertFalse(savedFoos.contains(foos.get(0)));
}
}
public类MockConnection实现IConnection{
私人名单;
公共列表findFoos(){
返回foos;
}
公共void setFoos(列出foos){
this.foos=foos;
}
}
公共类MockParser实现IParser{
private int[]keepIndexes=new int[0];
公共列表解析(列表原始oos){
List parsedFoos=new ArrayList();
对于(int i=0;i// 'mockery' is the central framework object and Mock object factory
IParser mockParser = mockery.NewMock<IParser>();
// Other dependencies omitted
Job job = new Job(mockParser);
// This just ensures this method is called so the return value doesn't matter
Expect.Once.On(mockParser).
.Method("Parse").
.WithAnyArguments().
.Will(Return.Value(new object()));
job.Run();
mockery.VerifyAllExpectationsHaveBeenMet();