Java 可观察/列表或属性:侦听器中引发的异常不是“;见;通过单元测试

Java 可观察/列表或属性:侦听器中引发的异常不是“;见;通过单元测试,java,javafx,junit,properties,observablelist,Java,Javafx,Junit,Properties,Observablelist,场景:一个具有某个可观察字段(不管是简单属性还是可观察列表,都不重要)的类以及该字段的侦听器。如果客户端代码试图将可观察对象的值更改为任何无效值,侦听器将抛出异常 测试此行为时,异常会按预期抛出(显示在控制台上),但测试方法没有看到它。这是一个期望异常将失败的测试 感觉我错过了一些明显的东西: 为什么会发生这种情况 我的设置/期望有什么问题吗 如何修复:使测试通过,或更改设置或其他任何内容 例如: import org.junit.Test; import org.junit.runner.

场景:一个具有某个可观察字段(不管是简单属性还是可观察列表,都不重要)的类以及该字段的侦听器。如果客户端代码试图将可观察对象的值更改为任何无效值,侦听器将抛出异常

测试此行为时,异常会按预期抛出(显示在控制台上),但测试方法没有看到它。这是一个期望异常将失败的测试

感觉我错过了一些明显的东西:

  • 为什么会发生这种情况
  • 我的设置/期望有什么问题吗
  • 如何修复:使测试通过,或更改设置或其他任何内容
例如:

import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;

import javafx.beans.property.Property;
import javafx.beans.property.SimpleObjectProperty;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ListChangeListener.Change;
import javafx.collections.ObservableList;

/**
 * Trying to dig into issue with expected exceptions. They are shown
 * on the console, but not seen by test runner.
 * 
 * @author Jeanette Winzenburg, Berlin
 */
@RunWith(JUnit4.class)

public class ExceptionFailingTest {

    @Test (expected = IllegalStateException.class)
    public void testExceptionFromListChangeListener() {
        ListOwner owner = new ListOwner();
        owner.getObjects().clear();
    }

    /**
     * Exception thrown in a ChangeListener is not seen by test.
     */
    @Test (expected = IllegalStateException.class)
    public void testExceptionFromPropertyListener() {
        ListOwner owner = new ListOwner();
        owner.getProperty().setValue(null);
    }

    public static class ListOwner {

        private ObservableList objects;
        private Property property;

        public ListOwner() {
            objects = FXCollections.observableArrayList("some", "things", "in", "me");
            objects.addListener((ListChangeListener)c -> objectsChanged(c));
            property = new SimpleObjectProperty(this, "property", "initial");
            property.addListener((src, ov, nv) -> propertyChanged(ov));
        }

        public Property getProperty() {
            return property;
        }

        protected void propertyChanged(Object ov) {
            if (property.getValue() == null)
                throw new IllegalStateException("property must not be empty");
        }

        public ObservableList getObjects() {
            return objects;
        }

        protected void objectsChanged(Change c) {
            if (c.getList().isEmpty())
                throw new IllegalStateException("objects must not be empty");
        }
    }
}

如注释中所述,侦听器引发的异常基本上被抑制。这实际上似乎是一个合理的API设计选择(尽管有些文档会很好):在调用更改侦听器(或列表更改侦听器)时,属性或列表的值已经更改。因此,如果您有多个侦听器,一个侦听器抛出异常将有效地否决观察更改的其他侦听器,但不会否决更改本身。也很难看到(JPA风格的)“回滚”是如何实现的:如果第二个侦听器抛出异常,那么第一个侦听器必须以某种方式“未被注意”

因此,我认为否决更改的方法根本不是使用侦听器,而是通过对适当的属性/列表类进行子类化,并重写修改可观察对象的适当方法

例如,要获得一个从不为空的可观察列表,您可以执行如下操作[警告:不打算考虑生产质量,只是方法的一个示例]:

import java.util.List;

import javafx.collections.ModifiableObservableListBase;

public class NonEmptyObservableList<E> extends ModifiableObservableListBase<E> {

    private final List<E> source ;

    public NonEmptyObservableList(List<E> source) {
        if (source.isEmpty()) {
            throw new IllegalStateException("List cannot be empty");
        }
        this.source = source ;
    }


    @Override
    public E get(int index) {
        return source.get(index);
    }

    @Override
    public int size() {
        return source.size();
    }

    @Override
    protected void doAdd(int index, E element) {
        source.add(index, element);
    }

    @Override
    protected E doSet(int index, E element) {
        return source.set(index, element);
    }

    @Override
    protected E doRemove(int index) {
        if (size() <= 1) {
            throw new IllegalStateException("List cannot be empty");
        }
        return source.remove(index);
    }

}
您可以考虑重写<代码>()(代码)>“原子性否决”更改:

@Override
public void clear() {
    throw new IllegalStateException("List cannot be empty");
}
这将在调用
clear()
时保持列表不变(而不是在其中保留一个元素),尽管很难涵盖所有可能性(
list.subList(0,list.size()).clear()
…)


尝试创建一个通用的可否决的可观察列表(使用
谓词
来确定是否允许更改)是很有意思的,但是以一种有效的方式这样做将是相当具有挑战性的(并且留给读者作为练习)。

只是一个注释。一年后,在
TestFX
中实现了所需的功能


调用侦听器()的代码捕获侦听器抛出的异常,并将它们直接传递给FX应用程序线程的未捕获异常处理程序,防止它们传播到测试用例中。这在……中有记录。。。。哦,开玩笑而已。它似乎根本没有被记录在案。我认为否决更改的唯一方法是对属性实现进行子类化,并重写
set
setValue
。列表更难,但可能。@James\u D darn再次。。。谢谢是的,通常情况下,属性(在其无效状态下)是这样的,所以在拥有那个该死的列表之前,从来没有注意到它不能是空的。隐马尔可夫模型。。。所以回到正题,我们想把列表包装成一个子类SimpleListProperty并签入invalidated:dead end,因为invalidated是从。。。包装列表的ListChangeListener;)谢谢,这让我找到了一个可能的解决方案:-)事实上,我们在fx内部已经有了一个可否决列表(称为VetoableListDecorator),不确定它的状态是什么-在FX2大检修之前,列表实现严重缺乏。。另一方面,问这个问题通常会让我重新思考我的真正需求,这似乎是列表中的“如果没有,就有一个特定的替代元素”。我可以将其包装到ListProperty中,并在其invalidated中添加/删除代理。@kleopatra可能有一种简单的方法来实现最后一个选项,使用。一直在考虑它,但它是不可修改的,所以我不能公开它供用户修改。顺便说一句,我们不得不退出ListProperty技巧(要求就像我最后的评论加上“如果不是空的话,请再次删除代理”…嘟…我的大脑在睡觉还是什么;-)我们不能在一个侦听器中更改列表(技巧就是这么做的)-通过添加而不通过删除。看起来像您建议的自定义impl可能是唯一的选项含义:自定义TransformationList可以根据需要进行修改-但它又是一个自定义实现。。。也许应该让我的潜意识做一些工作,然后再继续,雾需要解决;-)@kleopatra-JavaFX似乎没有“可否决的更改侦听器”功能。这是故意的吗?还是由负责开发人员的离职导致的副作用?如果可能的话,你有没有看到这样的地方?这类似于传统的“受约束属性”,其中新值在设置为属性之前进行测试,即它是..不是。。(i)设置值,(ii)检查是否有效,然后(iii)如果更改被拒绝,则回滚。感谢您提供的信息-就我个人而言,我不使用TestFX,但可能最好让其他人知道:)
@Override
public void clear() {
    throw new IllegalStateException("List cannot be empty");
}