测试一个Swing组件的单元测试

15
我正在编写一个类似于TotalCommander的应用程序。我有一个文件列表的单独组件和一个它的模型。该模型支持侦听器,并以以下方式发布事件通知,例如 CurrentDirChanged 等:
private void fireCurrentDirectoryChanged(final IFile dir) {
    if (SwingUtilities.isEventDispatchThread())
        for (FileTableEventsListener listener : tableListeners)
            listener.currentDirectoryChanged(dir);
    else {
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                for (FileTableEventsListener listener : tableListeners)
                    listener.currentDirectoryChanged(dir);
            }
        });
    }
}
我已经为此编写了一个简单的测试:
@Test
public void testEvents() throws IOException {
    IFile testDir = mockDirectoryStructure();
    final FileSystemEventsListener listener = 
                context.mock(FileSystemEventsListener.class);
    context.checking(new Expectations() {{
        oneOf(listener).currentDirectoryChanged(with(any(IFile.class)));
    }});
FileTableModel model = new FileTableModel(testDir); model.switchToInnerDirectory(1); }
这并不起作用,因为没有EventDispatchThread。是否有任何方法在无头构建中对其进行单元测试?
单位测试Java Swing JMock
5个回答

14
注意,总的来说在UI界面上进行单元测试通常都很困难,因为你必须模拟出许多不可用的东西。
因此,在开发任何类型的应用程序时,主要目标始终是尽可能地将UI与主要应用程序逻辑分离开来。在这里有很强的依赖关系,使得单元测试变得非常困难,基本上是一场噩梦。通常可以通过使用类似MVC的模式来实现这一点,其中您主要测试控制器类和您的视图类仅构造UI并将其操作和事件委派给控制器。这将责任分离,使测试更容易。
此外,您不必测试框架已经提供的内容,比如测试事件是否正确触发。您应该只测试自己编写的逻辑。

1
我编写了这段代码,想要测试它在应该触发事件并且参数正确的情况下是否能够正常工作。我猜想,我在这里做错的是在模型内确保GUI线程。模型不是一个Swing组件,因此不必在GUI线程内触发事件。我的想法正确吗? - Ula Krukar

11

抱歉不接受你的答案,但我并不想测试GUI,我只想测试我的模型是否存在问题。 - Ula Krukar
1
AssertJ库是FEST的现代分支,我已经成功地使用过。 - Shaun

2

请查看uispec4j项目。这是我用来测试用户界面的工具。

www.uispec4j.org


2
似乎是一个已经废弃的项目?(链接似乎不再指向之前的位置) - Snappawapa

2

我认为测试的问题在于揭示了代码中的问题。模型本不应该决定它是否在分派线程中运行,这是太多责任了。它只应该执行其通知工作,并让调用组件决定直接调用它还是调用invokeLater。该组件应位于知道Swing线程的代码部分。此组件仅应了解文件等内容。


1
我只使用jMock工作了两天...所以如果有更优雅的解决方案,请原谅我。 :)
看起来你的FileTableModel依赖于SwingUtilities...你考虑过模拟你使用的SwingUtilities吗?一种解决问题的方法是创建一个接口,比如ISwingUtilities,并实现一个虚拟类MySwingUtilities,它简单地转发到真正的SwingUtilities。然后在你的测试用例中,你可以模拟这个接口并返回isEventDispatchThread为true。
@Test
public void testEventsNow() throws IOException {
    IFile testDir = mockDirectoryStructure();

    final ISwingUtilities swingUtils = context.mock( ISwingUtilities.class );

    final FileSystemEventsListener listener = 
                context.mock(FileSystemEventsListener.class);

    context.checking(new Expectations()
    {{
        oneOf( swingUtils ).isEventDispatchThread();
            will( returnValue( true ) );

        oneOf(listener).currentDirectoryChanged(with(any(IFile.class)));
    }});

    FileTableModel model = new FileTableModel(testDir);
    model.setSwingUtilities( swingUtils ); // or use constructor injection if you prefer
    model.switchToInnerDirectory(1);
}

这基本上是我们的做法。我们用OurSwingUtilities.getInstance()替换掉SwingUtilities,然后在测试中,我们有一个替代实现(我们不使用jMock,因为很多测试可以更方便地共享一个类)。我们对SwingWorker.execute()以及许多静态工具进行了这样的处理,在Java库中也是如此。 - Hakanai

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接