Observable/List 或 属性:单元测试在侦听器中抛出的异常不是 "seen"
Observable/List or Property: exception thrown in listener not "seen" by unit test
场景:一个 class 带有一些可观察字段(无论是简单的 属性 还是 observableList,都没有关系),并且有一个对此字段的侦听器。如果客户端代码试图将 observable 的值更改为任何无效值,则侦听器会抛出异常。
测试此行为时,异常会按预期抛出(显示在控制台上),但测试方法看不到它。这是一个预期异常会失败的测试。
感觉好像遗漏了一些明显的东西:
- 为什么会这样?
- 我的 setup/expectation 有什么问题吗?
- 如何解决:要么让测试通过,要么改变设置或其他什么?
例子:
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 风格)"rollback" 是如何实现的:如果第二个侦听器抛出异常,那么第一个侦听器必须以某种方式 "unnotified"。
所以我认为否决更改的方法根本不是听众,而是通过子class适当的property/list class并覆盖修改可观察对象的适当方法.
例如,要有一个永远不会为空的可观察列表,您可以执行类似以下操作[注意:不是为了生产质量,只是方法的说明]:
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);
}
}
请注意,在此列表上调用 clear()
会有些随意地保留 "last" 元素。这是一个单元测试:
import java.util.ArrayList;
import java.util.Arrays;
import org.junit.Test;
import org.junit.Assert;
public class NonEmptyObservableListTest {
@Test (expected = IllegalStateException.class)
public void testExceptionFromListChangeListener() {
NonEmptyObservableList<String> list = new NonEmptyObservableList<>(new ArrayList<>(Arrays.asList("one", "two")));
list.clear();
}
@Test
public void testSizeOnClear() {
NonEmptyObservableList<String> list = new NonEmptyObservableList<>(new ArrayList<>(Arrays.asList("one", "two")));
try {
list.clear();
} catch (Exception e) {
// squash exception to test list size...
}
Assert.assertSame("List size is not 1", list.size(), 1);
}
}
您也可以考虑将 clear()
覆盖为 "atomically veto" 更改:
@Override
public void clear() {
throw new IllegalStateException("List cannot be empty");
}
这将使列表在调用 clear()
时保持不变(而不是在其中留下一个元素),尽管很难涵盖所有可能性 (list.subList(0, list.size()).clear()
...)。
尝试创建一个通用的可否决可观察列表(使用 Predicate<List<E>>
来确定是否允许更改)会很有趣,但以有效的方式这样做将非常具有挑战性(并且被保留作为 reader).
的练习
请注意。一年后在 TestFX
.
中实现了所需的功能
场景:一个 class 带有一些可观察字段(无论是简单的 属性 还是 observableList,都没有关系),并且有一个对此字段的侦听器。如果客户端代码试图将 observable 的值更改为任何无效值,则侦听器会抛出异常。
测试此行为时,异常会按预期抛出(显示在控制台上),但测试方法看不到它。这是一个预期异常会失败的测试。
感觉好像遗漏了一些明显的东西:
- 为什么会这样?
- 我的 setup/expectation 有什么问题吗?
- 如何解决:要么让测试通过,要么改变设置或其他什么?
例子:
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 风格)"rollback" 是如何实现的:如果第二个侦听器抛出异常,那么第一个侦听器必须以某种方式 "unnotified"。
所以我认为否决更改的方法根本不是听众,而是通过子class适当的property/list class并覆盖修改可观察对象的适当方法.
例如,要有一个永远不会为空的可观察列表,您可以执行类似以下操作[注意:不是为了生产质量,只是方法的说明]:
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);
}
}
请注意,在此列表上调用 clear()
会有些随意地保留 "last" 元素。这是一个单元测试:
import java.util.ArrayList;
import java.util.Arrays;
import org.junit.Test;
import org.junit.Assert;
public class NonEmptyObservableListTest {
@Test (expected = IllegalStateException.class)
public void testExceptionFromListChangeListener() {
NonEmptyObservableList<String> list = new NonEmptyObservableList<>(new ArrayList<>(Arrays.asList("one", "two")));
list.clear();
}
@Test
public void testSizeOnClear() {
NonEmptyObservableList<String> list = new NonEmptyObservableList<>(new ArrayList<>(Arrays.asList("one", "two")));
try {
list.clear();
} catch (Exception e) {
// squash exception to test list size...
}
Assert.assertSame("List size is not 1", list.size(), 1);
}
}
您也可以考虑将 clear()
覆盖为 "atomically veto" 更改:
@Override
public void clear() {
throw new IllegalStateException("List cannot be empty");
}
这将使列表在调用 clear()
时保持不变(而不是在其中留下一个元素),尽管很难涵盖所有可能性 (list.subList(0, list.size()).clear()
...)。
尝试创建一个通用的可否决可观察列表(使用 Predicate<List<E>>
来确定是否允许更改)会很有趣,但以有效的方式这样做将非常具有挑战性(并且被保留作为 reader).
请注意。一年后在 TestFX
.