为什么我的 NPE 只在程序 运行 时偶尔出现?

Why Am I Getting An NPE That Only Appears Occasionally When The Program is Run?

我正在使用 BlueJ 中的 JUnit 为我的 GiftSelector class 编写测试 class。当我 运行 testGetCountForAllPresents() 方法时,我得到一个 NullPointerException 在线:

assertEquals(true, santasSelector.getCountsForAllPresents().get(banana) == 3);

关于这个 NPE 的 st运行ge 事情是,当我 运行 测试一次时它很少出现,但在我 运行 测试时经常出现。它有时直到我连续 运行 测试 7-8 次后才会出现。

我收到的错误消息是: 没有异常消息。

NPE at line 215 in GiftSelectortest.testGetCountForAllPresents

我的测试代码 class 是:

import static org.junit.Assert.*;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

/**
 * The test class GiftSelectorTest. The GiftSelector that you are 
 * testing must have testMode enabled for this class to function. 
 * This is done in the setUp() method.
 */
public class GiftSelectorTest
{
    private GiftList giftList1;
    private GiftList giftList2;
    private GiftList giftList3;
    private Child jack;
    private Child bob;
    private Child dave;
    private Child naughty1;
    private GiftSelector santasSelector;
    private Present banana1;
    private Present orange;
    private Present banana;
    private Present apple;
    private Present bike;
    private Present doll;
    private Present got;
    private Present pearlHarbour;
    private Present dog;
    private Present cat;
    private Present ball;
    private Present heineken;

    /**
     * Default constructor for test class GiftSelectorTest
     */
    public GiftSelectorTest()
    {
        //Nothing to do here...
    }

    /**
     * Sets up the test fixture.
     *
     * Called before every test case method.
     */
    @Before
    public void setUp()
    {
        santasSelector = new GiftSelector();
        santasSelector.setTestMode(true);
        jack = new Child("Jack", 20, "1 A Place", true, true, true, false);
        bob = new Child("Bob", 10, "2 A Place", true, true, true, true);
        dave = new Child("Dave", 10, "3 A Place", true, true, true, true);
        naughty1 = new Child("John", 5, "4 A Place", true, true, true, true);
        giftList1 = new GiftList(jack);
        giftList2 = new GiftList(bob);
        giftList3 = new GiftList(dave);
        banana = new Present("banana", "fruit", 10);
        orange = new Present("orange", "fruit", 10);
        banana1 = new Present("banana", "fruit", 10);
        apple = new Present("apple", "fruit", 10);
        bike = new Present("bike", "toy", 200);
        doll = new Present("doll", "toy", 40);
        got = new Present("game of thrones", "dvd", 50);
        pearlHarbour = new Present("pearl harbour", "dvd", 20);
        dog = new Present("dog", "animal", 100);
        cat = new Present("cat", "animal", 80);
        ball = new Present("ball", "toy", 5);
        heineken = new Present("heineken", "beer", 1.60);
    }

    /**
     * Tears down the test fixture.
     *
     * Called after every test case method.
     */
    @After
    public void tearDown()
    {
        //Nothing to do here...
    }


    @Test
    public void testGetCountForAllPresents()
    {
        System.out.println(santasSelector.getCountsForAllPresents());
        //Test on empty GiftSelector
        assertNull(santasSelector.getCountsForAllPresents());

        //Test on a GiftSelector with one giftlist containing one present
        giftList1.addPresent(banana);
        santasSelector.addGiftList(giftList1);
        System.out.println(santasSelector.getCountsForAllPresents());
        assertEquals(true, santasSelector.getCountsForAllPresents().get(banana) == 1);

        //Test when GiftSelector contains 2 giftlists, each containing the same present object

        giftList2.addPresent(banana);
        santasSelector.addGiftList(giftList2);
        System.out.println(santasSelector.getCountsForAllPresents());
        assertEquals(true, santasSelector.getCountsForAllPresents().get(banana) == 2);

        //Test when GiftSelector contains 3 giftlists, 2 containing the same present object and another containing an identical present but with a different present instance
        giftList3.addPresent(banana1);
        santasSelector.addGiftList(giftList3);
        System.out.println(santasSelector.getCountsForAllPresents());
        assertEquals(true, santasSelector.getCountsForAllPresents().get(banana) == 3); //This is the line I get the NPE

        //Test when GiftSelector contains 3 giftLists, the first with one with a banana, the second with a banana and apple, and the third with a banana1 and ball
        giftList2.addPresent(apple);
        giftList3.addPresent(ball);
        System.out.println(santasSelector.getCountsForAllPresents());
        assertEquals(true, santasSelector.getCountsForAllPresents().get(banana) == 3);
        assertEquals(true, santasSelector.getCountsForAllPresents().get(apple) == 1);
        assertEquals(true, santasSelector.getCountsForAllPresents().get(ball) == 1);

    }


    @Test
    public void testGetMostPopularPresent()
    {
        //Test on empty GiftSelector
        assertNull(santasSelector.getMostPopularPresent());

        //Test on a GiftSelector with one giftList and one Present
        giftList1.addPresent(heineken);
        santasSelector.addGiftList(giftList1);
        assertEquals(true, santasSelector.getMostPopularPresent().comparePresent(heineken));

        //Tset on a GiftSelector with 1 giftList and 2 presents, one more expensive than the other
        giftList1.addPresent(banana);
        assertEquals(true, santasSelector.getMostPopularPresent().comparePresent(banana));

        //Test on a GiftSelector with 1 giftList and 3 presents. Banana and Apple are equal in price, and are both in the top3, 
        //therefore it should return the present closest to the start of the list
        giftList1.addPresent(apple);
        assertEquals(true, santasSelector.getMostPopularPresent().comparePresent(banana) || santasSelector.getMostPopularPresent().comparePresent(apple));

        //Test on a GiftSelector with 2 giftLists, the second list containing banana1, an indentical present to banana
        giftList2.addPresent(banana1);
        santasSelector.addGiftList(giftList2);
        assertEquals(true, santasSelector.getMostPopularPresent().comparePresent(banana));

        //Test on a GiftSelector with 2 giftLists, the first containing four presents and the second containing 2 presents.
        //This tests to see if top3 is working.
        giftList1.addPresent(bike);
        giftList2.addPresent(bike);
        assertEquals(true, santasSelector.getMostPopularPresent().comparePresent(bike));
    }
}

我只包含了引用 getCountsForAllPresents() 方法的测试方法。您会注意到我在每次调用包含 getCountForAllPresents() 方法的 assertEquals() 方法之前添加了打印语句。有趣的是,在我得到 NPE 的那一行之前,print 语句打印出 getCountForAllPresents().

返回的 HashMap 的正确值

我注意到的唯一其他 st运行ge 事情是,当我使用 BlueJ 的内置调试器执行 testGetCountForAllPresents() 方法时,我注意到 giftList3 没有't appear in the santaMap HashMap in santasSelector, 但是 print 语句仍然打印正确的计数,暗示它必须知道 giftList3.

getCountForAllPresents()的代码是:

/**
 * For each present, calculate the total number of children who have asked for that present.
 * 
 * @return - a Map where Present objects are the keys and Integers (number of children requesting
 * a particular present) are the values. Returns null if santaMap is empty.
 */
public HashMap<Present, Integer> getCountsForAllPresents()
{
    if(!santaMap.isEmpty()) {
        //This HashMap contains a mapping from each unique real world present, represented by it's toComparisonString(), to a Present object representing it
        HashMap<String, Present> uniquePresents = new HashMap<String, Present>();
        //This HashMap contains a mapping from each Present object in uniquePresents to the number of times it's toComparisonString() is equal to another in santaMap
        HashMap<Present, Integer> presentFrequency = new HashMap<Present, Integer>();

         for(GiftList wishlist: santaMap.values()) {
            for(Present present: wishlist.getAllPresents()) {
                //Have we already seen this present?
                if(uniquePresents.containsKey(present.toComparisonString())) {
                    //If so, update the count in presentFrequency
                    Integer tmp = presentFrequency.get(uniquePresents.get(present.toComparisonString()));
                    tmp++;
                    presentFrequency.put(uniquePresents.get(present.toComparisonString()), tmp);
                } else {
                    //If not, add it to the maps uniquePresents and presentFrequency (with a frequency of 1)
                    uniquePresents.put(present.toComparisonString(), present);
                    presentFrequency.put(present, 1);
                }
            }
        }
        //Return a map with unique presents as keys and their frequencies as values
        return presentFrequency;
    }
    else {
        //If there are no mappings in Santa's map, return null
        return null;
    }
}

我应该解释一下 santaMap 是一个 HashMap,其中 Child object 作为键,GiftList object 作为价值。它基本上将 child 映射到他们的圣诞愿望清单。一个 santaMap 只能包含同一个 child.

的一个心愿单

我不知道为什么我会得到 NPE,这与我编写 getCountForAllPresents() 方法的方式有关吗?我是如何实施测试的 method/class?

您的 Present class 不会覆盖 hashCode()equals()。这意味着 banana1banana 是任何 HashMap 中的两个不同的键,将使用它们作为键。

让我们看看这里发生了什么。您有 bananabanana1 个对象 - 第一个对象中的两个,第二个对象中的一个。

getCountsForAllPresents() 中,您有两个哈希映射。第一个通过对象的比较字符串,第二个 - 通过对象本身。

添加遇到的第一根香蕉。如果它是 banana 对象,你将得到类似这样的东西:

uniquePresents
banana-fruit-10 ➞ [banana instance]

presentFrequency
[banana instance] ➞ Integer(1)

你继续迭代。您遇到下一个 banana 对象。这是同一个对象。你会得到:

uniquePresents
banana-fruit-10 ➞ [banana instance]

presentFrequency
[banana instance] ➞ Integer(2)

现在您到达 banana1 对象。这是一个不同的对象,但它具有相同的比较字符串!会发生什么?

此条件为真:uniquePresents.containsKey(present.toComparisonString())。这意味着它进入 if 的真实部分。

Integer tmp = presentFrequency.get(uniquePresents.get(present.toComparisonString()));

这意味着它将获取 banana-fruit-10 当前指向的对象,即 banana 对象 - 而不是 banana1 对象, 获取其关联的频率,并增加它。它也由同一个对象存储。你现在拥有的是:

uniquePresents
banana-fruit-10 ➞ [banana instance]

presentFrequency
[banana instance] ➞ Integer(3)

请注意 presentFrequency 根本没有 banana1 键。现在你 return 这个对象。

当您尝试通过 banana 检索时,它工作正常 - 断言工作。

但是请记住,santaMap本身就是一个HashMap。这意味着没有保证订单。迭代器可能会给你 giftList1giftList2giftList3,但它也可能会给你 giftList3giftList1giftList2 - 或任何其他订单。

那么当它首​​先给你 giftList3 时会发生什么?你最终会得到:

uniquePresents
banana-fruit-10 ➞ [banana1 instance]

presentFrequency
[banana1 instance] ➞ Integer(3)

为什么?因为banana1是第一个带钥匙banana-fruit-10的礼物,以后也是用的

发生这种情况时,当您尝试从 returned 对象获取 banana 时,频率列表中不存在该键。它 returns null - 还有你的 NullPointerException.