我一直在做一个有很多UI组件的项目。因为所有的组件都是基于MVC模式的,所以它们被构造成一个组件——公共接口和工厂,包保护的模型/视图/控制器。
"手工"测试它们——使用模拟技术太困难且耗时。
于是我打开了Fest框架- http://fest.easytesting.org/。这很简单,很好,而且很有效。
当我尝试同时使用JMockit - http://code.google.com/p/jmockit/和Fest时,问题出现了。我注意到Fest使用了一些可能与JMockit冲突的库——反射和断言。
当我运行测试时,JMockit不模拟所需的类。我以前使用过JMockit,所以我很确定这是库之间的某种冲突。在被模拟的类上没有生成$Proxy$,当然,类的行为会出错。
mock是必需的,因为我必须测试完整的组件交互!
版本:
JMockit:0.999.8
节:
Fest-swing 1.2.1Fest-assert 1.4Fest-util 1.1.6Fest-reflect 1.2
我不打算通过查看两个库来寻找冲突,所以任何帮助都是非常感谢的。
谢谢。
更新:
测试/示例代码在这里:
public interface SimpleControllable {
void init();
JPanel getPanel();
}
public class SimpleController implements SimpleControllable {
private final SimpleForm simpleForm;
private final SimpleModel simpleModel;
public SimpleController(
final SimpleForm simpleForm,
final SimpleModel simpleModel
) {
this.simpleForm = simpleForm;
this.simpleModel = simpleModel;
}
@Override
public void init() {
simpleForm.init();
//This works!
/*simpleForm.getTestButton().addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
simpleForm.getTestButton().setText(simpleModel.getSimpleValue());
}
});*/
//This doesn't!
simpleForm.getTestButton().setText(simpleModel.getSimpleValue());
}
@Override
public JPanel getPanel() {
return simpleForm.getTestPanel();
}
}
public class SimpleModel {
private final SimpleServable simpleServable;
public SimpleModel(final SimpleServable simpleServable) {
this.simpleServable = simpleServable;
}
public String getSimpleValue() {
return simpleServable.getValue();
}
}
public interface SimpleServable {
String getValue();
}
public class SimpleService implements SimpleServable {
private String value;
@Override
public String getValue() {
return value;
}
}
public class SimpleForm {
private JButton testButton;
private JPanel testPanel;
public void init() {
testPanel.setName("testPanel");
testButton.setName("testButton");
}
public JButton getTestButton() {
return testButton;
}
public JPanel getTestPanel() {
return testPanel;
}
}
public class SimpleTest extends FestSwingJUnitTestCase {
@Mocked
private SimpleService simpleServable;
private FrameFixture window;
@Override
protected void onSetUp() {
FailOnThreadViolationRepaintManager.install();
JFrame frameUi = GuiActionRunner.execute(new GuiQuery<JFrame>() {
protected JFrame executeInEDT() {
SimpleControllable simpleControllable = getSimpleControllable();
JFrame frame = new JFrame("TEST");
frame.add(simpleControllable.getPanel());
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
frame.pack();
return frame;
}
});
robot().settings().delayBetweenEvents(1000);
// IMPORTANT: note the call to 'robot()'
// we must use the Robot from FestSwingTestngTestCase
window = new FrameFixture(robot(), frameUi);
window.show(); // shows the frameU
}
//Should use factory, but not neccesary for test purposes...
private SimpleControllable getSimpleControllable() {
SimpleForm simpleForm = new SimpleForm();
//SimpleServable simpleServable = new SimpleService();
SimpleModel simpleModel = new SimpleModel(simpleServable);
SimpleControllable simpleControllable = new SimpleController(
simpleForm,
simpleModel
);
//Initialize the component, therein lies the problem...
simpleControllable.init();
return simpleControllable;
}
@Test
public void test() throws Exception {
//Before
new Expectations() {
{
simpleServable.getValue();
result = "TEST";
}
};
//When
//This works!
// window.panel("testPanel").button("testButton").click().requireText("TEST");
//This doesn't!
window.panel("testPanel").button("testButton").requireText("TEST");
//Then
}
}
我似乎太早责备了框架。但我仍然不明白为什么它不起作用的细节。类Service是模拟的,即使延迟了预期,它仍然应该是可用的。我理解时间问题(组件的初始化),但不知道如何"修复"这个
谢谢。
更新2:
谢谢,Rogerio。您可以使用FEST测试组件,但它并没有真正利用JMockit,而且有些类具有相当多的方法(是的,我知道- SRP,但让我们尝试保持这种方式),并且将从JMockit这样的模拟框架中受益匪浅。我在这里发布问题之前使用了这个,所以你可以自己使用它,并理解这不是我想要的方式:
public class SimpleTest extends FestSwingJUnitTestCase {
//This works, and I used this mechanism before, but this is without the help of JMockit.
//If you have a lot of methods you want to mock this test turns into chaos.
public static class MockSimpleServable implements SimpleServable{
@Override
public String getValue() {
return "TEST";
}
}
// @Mocked
// private SimpleServable simpleServable;
private FrameFixture window;
@Override
protected void onSetUp() {
FailOnThreadViolationRepaintManager.install();
JFrame frameUi = GuiActionRunner.execute(new GuiQuery<JFrame>() {
protected JFrame executeInEDT() {
SimpleControllable simpleControllable = getSimpleControllable();
JFrame frame = new JFrame("TEST");
frame.add(simpleControllable.getPanel());
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
frame.pack();
return frame;
}
});
robot().settings().delayBetweenEvents(1000);
// IMPORTANT: note the call to 'robot()'
// we must use the Robot from FestSwingTestngTestCase
window = new FrameFixture(robot(), frameUi);
window.show(); // shows the frameU
}
//Should use factory, but not neccesary for test purposes...
private SimpleControllable getSimpleControllable() {
SimpleForm simpleForm = new SimpleForm();
//SimpleServable simpleServable = new SimpleService();
SimpleServable simpleServable = new MockSimpleServable();
SimpleModel simpleModel = new SimpleModel(simpleServable);
SimpleControllable simpleControllable = new SimpleController(
simpleForm,
simpleModel
);
//Initialize the component, therein lies the problem...
simpleControllable.init();
return simpleControllable;
}
@Test
public void test() throws Exception {
//This works!
// window.panel("testPanel").button("testButton").click().requireText("TEST");
//This doesn't!
window.panel("testPanel").button("testButton").requireText("TEST");
}
}
问题仍然存在-有没有人知道我可以用JMockit测试这个的方法,不要忘记EDT冲突
问题在测试代码中,它在窗口构建期间碰巧调用了SimpleServable#getValue()
, 在之前,该方法调用的期望被记录在测试中。
要解决这个问题,只需将new Expectation() {{ ... }}
块移动到onSetUp()
方法,将其放在对GuiActionRunner.execute(GuiQuery)
的调用之前。
public final class SimpleForm
{
final JPanel testPanel;
final JButton testButton;
public SimpleForm()
{
testPanel = new JPanel();
testPanel.setName("testPanel");
testButton = new JButton();
testButton.setName("testButton");
testPanel.add(testButton);
}
}
public final class SimpleService {
public String getValue() { return null; }
}
public final class SimpleController
{
private final SimpleForm form;
public SimpleController()
{
form = new SimpleForm();
SimpleService service = new SimpleService();
form.testButton.setText(service.getValue());
}
public JPanel getPanel() { return form.testPanel; }
}
public final class SimpleTest extends FestSwingJUnitTestCase
{
FrameFixture window;
@NonStrict SimpleService service;
@Override
protected void onSetUp()
{
FailOnThreadViolationRepaintManager.install();
// Record expectations *before* they are replayed:
new Expectations() {{ service.getValue(); result = "TEST"; }};
JFrame frameUi = GuiActionRunner.execute(new GuiQuery<JFrame>() {
@Override
protected JFrame executeInEDT()
{
SimpleController controller = new SimpleController();
JFrame frame = new JFrame("TEST");
frame.add(controller.getPanel());
frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
frame.pack();
return frame;
}
});
robot().settings().delayBetweenEvents(1000);
window = new FrameFixture(robot(), frameUi);
window.show();
}
@Test
public void test()
{
// This works provided "service.getValue()" gets called *after*
// expectations are recorded. With the call happening during the
// creation of the window, it must be recorded at the beginning
// of the "onSetUp" method.
window.panel("testPanel").button("testButton").requireText("TEST");
}
}