2014-10-30 4 views
2

У меня есть класс Singleton для теста:JMockit: Singleton класс Заказать тест

public class Singleton { 
    private static Singleton instance; 

    private List<String> list; 

    private Singleton() { 

    } 

    public static Singleton getInstance() { 
     if (instance == null) { 
      synchronized (Singleton.class) { 
       instance = new Singleton(); 
      } 
     } 
     return instance; 
    } 

    public boolean methodOne() { 
     if (list == null) { 
      list = new ArrayList<String>(); 
      list = SomeClass.fillListOne(); 
     } 
     return SomeClass.verifyList(list); 
    } 

    public boolean methodTwo() { 
     if (list == null) { 
      list = new ArrayList<String>(); 
      list = SomeClass.fillListTwo(); 
     } 
     return SomeClass.verifyList(list); 
    } 
} 

С следующего теста:

@RunWith(JMockit.class) 
public class SingletonTest { 
    @Test 
    public void testOne(final @Mocked SomeClass someClass) { 
     Singleton.getInstance().methodOne(); 
     new Verifications() { 
      { 
       SomeClass.fillListOne(); 
      } 
     }; 

    } 

    @Test 
    public void testTwo(final @Mocked SomeClass someClass) { 
     Singleton.getInstance().methodTwo(); 
     new Verifications() { 
      { 
       SomeClass.fillListTwo(); 
      } 
     }; 
    } 
} 

Если я выполняю только "testOne" или только «testTwo », проходят тесты. Если я выполняю все тесты, он передает только первый выполненный метод. Как установить атрибут «list» равным null, например, в методе @Before? Как использовать Deencapsulation с Singleton или частным членом без сеттеров?

+0

FYI: метод getInstance невероятно опасен. Вы не можете синхронизировать внутри нулевой проверки и ожидать, что она будет безопасной. Входит Thread A и проверяет, не является ли экземпляр экземпляром null, но это не так. Контекстный коммутатор, Thread B входит, чтобы проверить, является ли экземпляр нулевым, он все еще не так продолжается. Thread B получает блокировку и инициализирует некоторый объект, присваивает его экземпляру (который гарантированно назначается и инициализируется в соответствии с требованиями синхронизации) и освобождает блокировку. Контекстный коммутатор Thread A теперь получает блокировку и делает то же самое, переписывая экземпляр из ThreadB. – searchengine27

+0

рекомендуется прочитать для разработчиков Java-совместимости: http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html (вы сделали самую первую ошибку, подробно описанную здесь) – searchengine27

+0

Уточнение: "(что гарантировано быть назначенным и инициализированным в соответствии с требованиями синхронизации) »означает, что к моменту освобождения блокировки гарантируется, что объект будет назначен и инициализирован. Порядок, который происходит внутри синхронизированного блока, не является детерминированным, для JMM. Для дальнейшего уточнения ваш пример становится жертвой этого, но также становится жертвой моего первоначального примера (который не имеет отношения к JMM, но зная, как JMM справится с этим, облегчит ваше «решение» этой проблемы, которое имеет ссылку I размещены детали, почему это не сработает). – searchengine27

ответ

2

Этот класс не особенно восприимчив к испытаниям. Я бы настоятельно рекомендовал вам переписать его, если у вас есть выбор. (Тестирование с издеваемыми статическими вызовами метода - это кошмар для обслуживания, и у меня также есть общая неприязнь ко всем вещам singleton-y - они, кажется, используются повсюду, должны ли они быть или нет).

Несмотря на это, the documentation suggests вы могли бы сделать что-то вроде этого (я также добавил дополнительное поле для удобства чтения, но вы не должны):

@RunWith(JMockit.class) 
public class SingletonTest { 
    private Singleton instance; 

    @Before 
    public void initialise() { 
     Deencapsulation.setField(Singleton.class, "instance", null); 
     instance = Singleton.getInstance(); 
    } 

    @Test 
    public void testOne(final @Mocked SomeClass someClass) { 
     instance.methodOne(); 
     new Verifications() { 
      { 
       SomeClass.fillListOne(); 
      } 
     }; 
    } 
    // ...other tests... 
} 
+0

СПАСИБО! Класс, который я написал, является упрощением более сложного класса, который я не могу редактировать. Я не хочу устанавливать null экземпляр, только список. Поэтому я редактирую ваш метод инициализации: instance = Singleton.getInstance(); Deencapsulation.setField (экземпляр, «список», нуль); – borras

+0

@borras Да, там, вот где неприятные вещи отражения, подобные приведенным выше, становятся полезными. Что касается сброса «списка» - ваш тест будет более надежным, если вы сбросите весь «экземпляр», а не только его часть (я предполагаю, что запущенные ресурсы не являются проблемой) – blgt

0

Я нашел проблему с существующим ответом: даже если методы SomeClass равны не называется, проходит тест. Это побеждает цель теста: он должен пройти тогда и только тогда, когда вызываются методы. Вызывается SomeClass.fillListOne() и SomeClass.fillListTwo().

Здесь вы можете проверить вызовы с помощью JMockit, используя NonStrictExpectations в зависимости от SomeClass для возврата null. Я протестировал его, используя тот же пример, который приведен в вопросе, он не работает, когда звонки не выполняются и проходят, когда они:

@RunWith(JMockit.class) 
public class SingletonTest { 
    // capturing ensures Jmockit will mock all SomeClass instances. 
    @Capturing 
    private SomeClass someClass; 

    @Before 
    public void prepareTests() { 
     new NonStrictExpectations() {{ 
      SomeClass.fillListOne(); result = null; 
      SomeClass.fillListTwo(); result = null; 
     }}; 
    } 

    @Test 
    public void testOne() { 
     Singleton.getInstance().methodOne(); 
     new Verifications() {{ 
      SomeClass.fillListOne(); 
     }}; 
    } 

    @Test 
    public void testTwo() { 
     Singleton.getInstance().methodTwo(); 
     new Verifications() {{ 
      SomeClass.fillListTwo(); 
     }}; 
    } 
}