【问题标题】:How to test if a List<? extends Object> is an UnmodifableList?如何测试列表是否<? extends Object> 是 UnmodifableList 吗?
【发布时间】:2012-01-11 23:39:33
【问题描述】:

我正在寻找一种方法来测试某个给定的 List 是否是不可修改的。

我有一个具有List&lt;NoMatter&gt; 的对象,为了提供诸如addNoMatter(NoMatter nm) 之类的方法,而不是允许API 客户端简单地执行.getNoMatters().add(nm); 我总是返回此列表的不可修改版本,因此客户端是仍然能够拥有列表。我这样做如下:

public List<NoMatter> getNoMatters() {
    return Collections.unmodifiableList(this.noMatters);
}

问题是,当我进行测试时,我根本无法检查此对象是否属于 UnmodifiableList 类型。我的第一次尝试是:

@Test
public void checkIfListIsImmutable(){
    assertTrue("List is not immutable", this.myObj.getNoMatters() instanceof UnmodifiableList);
}

我似乎无法导入UnmodifiableListjava.util.Collections$UnmodifiableRandomAccessList 类型,这就是我在控制台上尝试System.out.println(myObj.getNoMatters().getClass().getName()); 时得到的结果。

那我该如何实现呢???

PS.:我知道我可以通过以下测试:

@Test(expected = UnsupportedOperationException.class)
public void checkIfListIsImmutable(){
     this.myObj.getNoMatters().add(null);
}

编辑: 上面的测试并没有让我知道我处理的列表不是不可变的,因为我需要对每个可能修改我的原始列表的方法进行测试,包括 .remove ()、.clear()、.shuffle() 等!这就是为什么我认为这不是一个很好的方法。

>>> 但我仍然相信它甚至没有接近一个优雅的解决方案!

【问题讨论】:

  • 我建议使用“addAll(Collections.emptyList());”而不是 add(null) 来测试不可变。如果不可变,这将引发 UnsupportedOperationException,如果可变则不会修改列表。我同意修改列表以查看它是否是不可变的感觉不对,并且如果其他人使用该列表可能会导致问题。
  • @SteveZobell 的建议就像蛋糕上的樱桃。解决了 List 实际上是可变的问题。需要考虑的一件事是 UnmodifiableList 不是 Java API 中唯一的不可修改列表。 Collections.nCopies(..) 返回一个 Collections$CopiesList 的实例,该实例也是不可修改的。从 AbstractList 实现一个不可修改的列表非常容易,因此最好的办法是测试其行为。

标签: java unit-testing collections tdd


【解决方案1】:

我认为您的解决方案不仅合理,而且优雅。您想测试您不能修改列表,并且您的测试简洁地证明了这一点。测试类的名称是测试名称,而不是行为。在这种情况下,谁在乎名字?

同样,如果我想测试我不能将 null 传递给某个方法,我会在测试中传递 null 并期待 IllegalArgumentException。

【讨论】:

  • 你是对的,我同意你的观点(甚至赞成),但如果你想在第一次异常发生时测试停止后在同一个测试中测试多个列表,那就不太好!
  • 我习惯了 JUnit 3,所以 YMMV,但是你应该可以写 try { list.add(null); fail(); } catch (RuntimeException expected) { ; } 我什至为此做了一个名为 testfail 的 Eclipse 模板
  • 附注- 在上面的模板中,将 RuntimeException 更改为尽可能具体到您期望的异常,例如本例中为 UnsupportedOperationException。
  • 在某些时候,投入到单元测试中的努力达到了接近于零的回报。我想你已经达到了。你可以测试所有无数的可能性。您可以编写自己的 Immutable 包装器,该包装器会引发特定异常 MyUnsupportedOperationException,并假设这证明您的包装器存在。或者你可以试试 add(),也许是 remove(),然后就开心了。一些奇怪的类会抛出异常以进行删除,但允许您调用 clear() 的机会似乎很遥远。
【解决方案2】:

我实际上认为这是你最好的选择。您的替代方法(不太优雅)是检查类的名称。

this.myObj.getNoMatters().getClass().getSimpleName().equals("UnmodifiableCollection")

你的问题是包装的 UnmodifiableCollection 是包私有的。

我不认为在那里期待异常有什么问题,但这就是我。

【讨论】:

  • 尝试/捕捉并尝试添加?
【解决方案3】:

您可以使用Class#isInstance查看。

Collections.unmodifiableList(someList).getClass().isInstance(listToCheck);

/e1
以下返回false

Collections.unmodifiableList(new ArrayList<Object>()).getClass().isInstance(new ArrayList<Object>())

以下返回true

Collections.unmodifiableList(new ArrayList<Object>()).getClass().isInstance(Collections.unmodifiableList(new ArrayList<Object>()))

/e2
您的问题可能是因为 Collections#unmodifiableList 有时会返回 UnmodifiableList,而其余时间会返回 UnmodifiableRandomAccessList(代码见下文)。但是,由于UnmodifiableRandomAccessList 扩展了UnmodifiableList,如果你得到一个UnmodifiableList 的实例来检查你会很成功。

要获取UnmodifiableList 的实例,您可以使用Collections.unmodifiableList(new LinkedList&lt;Object&gt;())LinkedList 没有实现RandomAccess,因此不会返回UnmodifiableRandomAccessList 的实例。

Collections#unmodifiableList的代码:

public static <T> List<T> unmodifiableList(List<? extends T> list) {
        return (list instanceof RandomAccess ?
                new UnmodifiableRandomAccessList<>(list) :
                new UnmodifiableList<>(list));
}

UnmodifiableRandomAccessList的类头:

static class UnmodifiableRandomAccessList<E> extends UnmodifiableList<E> implements RandomAccess

【讨论】:

  • 当要检查的列表是ArrayListCollections.unmodifiedList(list) 时它都返回true,所以它不能正常工作
  • @RenatoGama 查看我的第二次编辑,我想我找到了您遇到的问题。
  • 嗨 - 您还可以检查 UnmodifiableCollection,它是不可修改列表和集合类型的基类。这可以从Collections.unmodifiableCollection(Collections.emptyList()).getClass()检索到
【解决方案4】:

你为什么要测试你的列表是不可变的?您的“替代”解决方案是要走的路:您应该专注于测试您的类的行为,而不是它的状态

我的意思是你并不真正关心你的方法是否返回UnmodifiableList 的实例或其他任何东西。谁在乎?你的测试当然不应该。如果稍后您更改实现以实现完全相同的行为,您的测试不应失败

那么你想测试什么?用户不能向列表中添加任何内容?然后编写一个测试(如您所建议的),将某些内容添加到列表中并期待异常。他们不能从列表中删除任何内容?对另一个测试做同样的事情。等等。

像这样测试负例确实很优雅,而且我发现它经常在类的功能覆盖中被遗忘。适当的报道应明确说明您的班级不允许什么,并说明在这些情况下的预期。

【讨论】:

  • 这绝对是正确的做法。感谢您提醒我测试状态和行为之间的区别。
【解决方案5】:

除了user949300 的答案,它指出确实应该测试行为,并包括Steve Zobell 的评论,他建议添加一个空列表,以防列表不是不可修改的进行修改,我在这里添加一个示例,说明如何检查列表的不可修改性。

代码尝试添加一个空列表,如果可能会抛出 IllegalArgumentException,否则为方便起见,返回原始列表,现在可以确定它是不可修改的。

/**
 * Ensures that a list is unmodifiable (i.e. that nothing can be added).
 *
 * Unmodifiable lists can e.g. be created by Collections.UnmodifiableList().
 *
 * @param list a list
 * @param <T> element type of the list
 * @return the list that is verified to be unmodifiable
 */
public static <T> List<T> verifyUnmodifiable(List<T> list) {
    try {
        list.addAll(Collections.emptyList());
    } catch (Exception e) {
        return list;
    }
    throw new IllegalArgumentException("List is modifiable.");
}

不确定是否还需要对 remove、clear 等进行测试。无法添加通常是不可修改的良好指示。

【讨论】:

    【解决方案6】:

    有点像黑客,但试试吧:

    import java.util.*;
    public class Main {
        public static void main(String[] args) {
            List<Integer> list=new LinkedList<Integer>();
            list.add(1);
            List<Integer> unmodifiableList=Collections.unmodifiableList(list);
            System.out.println(unmodifiableList.getClass());
            if(unmodifiableList.getClass().getName().contains("UnmodifiableList"))
                System.out.println(true);
        }
    }
    

    【讨论】:

      【解决方案7】:

      我希望像 Collections.examine(T element) 这样的东西可以在不实际修改集合的情况下完成这项工作。

      除了投票最多的答案,请注意Collections.EmptyList(至少)也是另一个不可修改的列表。

      所以,如果我真的必须创建一个实用程序作为一个肮脏的解决方案,我将添加我的“易于识别”元素并查看它是否抛出 UnsupportedOperationException,那么这意味着它是不可修改的。否则,我当然必须立即删除输入的内容。

      【讨论】:

        【解决方案8】:

        如果 API 难以测试,这通常意味着 API 需要改进。在这种情况下,问题是 API 应该返回只读列表,但是(由于我猜 JDK 中缺少这种类型)它返回了更通用的类型。

        我会考虑更改函数签名,以便它返回实际上是只读列表的类型,例如番石榴的ImmutableList

        根据定义,它是不能修改的,所以你根本不需要测试。这也减少了班级客户的困惑。

        【讨论】:

          【解决方案9】:

          SetListMapJava 10 开始支持 copyOf(...) 方法。
          它确保你得到一个不可变的集合/列表/映射。 如果参数已经是 不可变的集合/列表/映射,它只是返回它。 如果它是可修改/可变的,则 copyOf(...) 将元素/条目复制到不可变的集合/列表/映射中并返回它。
          但是,如果 Collections.unmodifiableSet(...)/unmodifiableList(...)/unmodifiableMap(...) 将一个可修改集/列表/映射设为不可修改,
          copyOf(...) 将所有元素/条目复制到一个不可变的集合/列表/映射中,以确保它不能被仍然持有可修改集合/列表/映射实例的人修改。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2011-12-04
            • 2010-09-14
            • 2013-11-05
            • 1970-01-01
            • 1970-01-01
            • 2015-12-15
            相关资源
            最近更新 更多