2012-01-20 12 views
34

के साथ लॉगर और लॉगरफ़ैक्टरी का मज़ाक उड़ा रहा है मेरे पास निम्न लॉगर है जो मैं नकल करना चाहता हूं, लेकिन लॉग प्रविष्टियों को सत्यापित करने के लिए सामग्री के लिए नहीं कहा जा रहा है।पावरमॉक और मॉकिटो

private static Logger logger = 
     LoggerFactory.getLogger(GoodbyeController.class); 

मैं किसी भी वर्ग कि LoggerFactory.getLogger() के लिए प्रयोग किया जाता है उपहास करने के लिए चाहते हैं, लेकिन मैं बाहर है कि कैसे करना है नहीं पा सके। यह वही है मैं अब तक के साथ समाप्त हो गया है:

@Before 
public void performBeforeEachTest() { 
    PowerMockito.mockStatic(LoggerFactory.class); 
    when(LoggerFactory.getLogger(GoodbyeController.class)). 
     thenReturn(loggerMock); 

    when(loggerMock.isDebugEnabled()).thenReturn(true); 
    doNothing().when(loggerMock).error(any(String.class)); 

    ... 
} 

मुझे पता करना चाहते हैं:

  1. मैं स्थिर LoggerFactory.getLogger() किसी भी वर्ग के लिए काम करने के लिए नकली कर सकते हैं?
  2. मैं केवल @Before में चलाने के लिए प्रतीत होता हूं और इस प्रकार मैं प्रति विधि विशेषताओं को बदलने के लिए प्रतीत नहीं कर सकता। क्या इसके चारों ओर एक रास्ता है?

संपादित निष्कर्ष:

मैंने सोचा कि मैं पहले से ही इस की कोशिश की और यह काम did not:

when(LoggerFactory.getLogger(any(Class.class))).thenReturn(loggerMock); 

लेकिन धन्यवाद, यह काम किया था।

हालांकि मैं करने के लिए अनगिनत रूपों की कोशिश की है:

when(loggerMock.isDebugEnabled()).thenReturn(true); 

मैं loggerMock @Before के बाहर अपने व्यवहार को बदलने के लिए नहीं मिल सकता है लेकिन यह केवल Coburtura के साथ होता है। क्लॉवर के साथ, कवरेज 100% दिखाता है लेकिन अभी भी कोई मुद्दा है।

@RunWith(PowerMockRunner.class) 
@PrepareForTest({LoggerFactory.class}) 
public class ExampleServiceTests { 

    @Mock 
    private Logger loggerMock; 
    private ExampleServiceservice = new ExampleService(); 

    @Before 
    public void performBeforeEachTest() { 
     PowerMockito.mockStatic(LoggerFactory.class); 
     when(LoggerFactory.getLogger(any(Class.class))). 
      thenReturn(loggerMock); 

     //PowerMockito.verifyStatic(); // fails 
    } 

    @Test 
    public void testIsDebugEnabled_True() throws Exception { 
     when(loggerMock.isDebugEnabled()).thenReturn(true); 
     doNothing().when(loggerMock).debug(any(String.class)); 

     assertThat(service.getMessage(), is("Hello null: 0")); 
     //verify(loggerMock, atLeast(1)).isDebugEnabled(); // fails 
    } 

    @Test 
    public void testIsDebugEnabled_False() throws Exception { 
     when(loggerMock.isDebugEnabled()).thenReturn(false); 
     doNothing().when(loggerMock).debug(any(String.class)); 

     assertThat(service.getMessage(), is("Hello null: 0")); 
     //verify(loggerMock, atLeast(1)).isDebugEnabled(); // fails 
    } 
} 

तिपतिया घास में मैं if(logger.isDebugEnabled()){ ब्लॉक के 100% कवरेज दिखाने:

public ExampleService{ 
    private static final Logger logger = 
      LoggerFactory.getLogger(ExampleService.class); 

    public String getMessage() {   
    if(logger.isDebugEnabled()){ 
     logger.debug("isDebugEnabled"); 
     logger.debug("isDebugEnabled"); 
    } 
    return "Hello world!"; 
    } 
    ... 
} 

तब मैं इस परीक्षण है:

मैं इस साधारण क्लास की है। लेकिन अगर मैं सत्यापित करने का प्रयास loggerMock:

verify(loggerMock, atLeast(1)).isDebugEnabled(); 

मैं शून्य बातचीत मिलता है। मैंने भी कोशिश की; @Before में लेकिन इसमें शून्य इंटरैक्शन भी हैं।

यह सिर्फ अजीब लगता है कि कोबर्टुरा if(logger.isDebugEnabled()){ दिखाता है क्योंकि 100% पूर्ण नहीं है, और क्लोवर करता है, लेकिन दोनों सहमत हैं कि सत्यापन विफल हो जाता है।

+0

क्या आपने @MockPolicy की कोशिश की है? [यहां उदाहरण] (https://code.google.com/p/powermock/wiki/MockPolicies) EasyMock शैली mocks के लिए हैं लेकिन Mockito के लिए अनुकूलित किया जा सकता है। –

उत्तर

40

@Mick भी स्थिर क्षेत्र के मालिक तैयार करने के लिए, उदाहरण के लिए प्रयास करें:

@PrepareForTest({GoodbyeController.class, LoggerFactory.class}) 

EDIT1: मैं बस एक छोटा सा उदाहरण बना लिया। सबसे पहले नियंत्रक:

import org.slf4j.Logger; 
import org.slf4j.LoggerFactory; 

public class Controller { 
    Logger logger = LoggerFactory.getLogger(Controller.class); 

    public void log() { logger.warn("yup"); } 
} 

फिर परीक्षण:

import org.junit.Test; 
import org.junit.runner.RunWith; 
import org.powermock.core.classloader.annotations.PrepareForTest; 
import org.powermock.modules.junit4.PowerMockRunner; 
import org.slf4j.Logger; 
import org.slf4j.LoggerFactory; 

import static org.mockito.Matchers.any; 
import static org.mockito.Matchers.anyString; 
import static org.mockito.Mockito.verify; 
import static org.powermock.api.mockito.PowerMockito.mock; 
import static org.powermock.api.mockito.PowerMockito.mockStatic; 
import static org.powermock.api.mockito.PowerMockito.when; 

@RunWith(PowerMockRunner.class) 
@PrepareForTest({Controller.class, LoggerFactory.class}) 
public class ControllerTest { 

    @Test 
    public void name() throws Exception { 
     mockStatic(LoggerFactory.class); 
     Logger logger = mock(Logger.class); 
     when(LoggerFactory.getLogger(any(Class.class))).thenReturn(logger); 

     new Controller().log(); 

     verify(logger).warn(anyString()); 
    } 
} 

नोट आयात! classpath में उल्लेखनीय libs: Mockito, PowerMock, JUnit, logback कोर, logback-क्लासिक, slf4j


EDIT2: यह एक लोकप्रिय प्रश्न हो रहा है के रूप में, मुझे लगता है कि अगर बाहर बिंदु करना चाहते हैं ये लॉग संदेश महत्वपूर्ण हैं और परीक्षण की आवश्यकता है, यानी वे सिस्टम सिस्टम की सुविधा/व्यावसायिक हिस्सा हैं, फिर एक वास्तविक निर्भरता पेश करते हैं जो स्पष्ट सिद्धांतों को लॉग करते हैं, विशेषताएं पूरे सिस्टम डिज़ाइन में बहुत बेहतर होंगी, इसके बजाय लॉगर के मानक और तकनीकी वर्गों के स्थिर कोड पर भरोसा करना।

इस मामले के लिए मैं वर्ग जैसे reportIncorrectUseOfYAndZForActionX या reportProgressStartedForActionX जैसी विधियों को तैयार करने की अनुशंसा करता हूं। कोड को पढ़ने वाले किसी भी व्यक्ति के लिए यह सुविधा दृश्यमान बनाने का लाभ होगा। लेकिन यह परीक्षणों को हासिल करने में भी मदद करेगा, इस विशेष सुविधा के कार्यान्वयन विवरण को बदलें।

इसलिए आपको PowerMock जैसे स्थिर मॉकिंग टूल की आवश्यकता नहीं होगी। मेरी राय में स्थिर कोड ठीक हो सकता है, लेकिन जैसे ही परीक्षण स्थिर व्यवहार को सत्यापित करने या नकल करने की मांग करता है, स्पष्ट रूप से प्रतिबिंबित करने और स्पष्ट निर्भरताओं को प्रस्तुत करना आवश्यक है।

+3

नोट: यदि आपने वहां दूसरे @ टेस्ट की कोशिश की है, तो आपको एक समस्या होगी। सत्यापित करें() एक अतिरिक्त परीक्षण में फिर से बुलाए जाने पर काम नहीं करेगा। यदि आप @ पहले, या नए var नामों का उपयोग नहीं करते हैं। क्लासलोडर केवल उनमें से एक बनाने जा रहा है, इसलिए आपके पास दो अलग-अलग स्थैतिक वर्ग नहीं हो सकते हैं। अलग-अलग वर्गों में अपने परीक्षणों को तोड़ दें। –

+4

दुर्भाग्य से मैं इसे काम नहीं कर सकता ... मॉकिटो कहता है .. वास्तव में, इस नकली के साथ शून्य बातचीत हुई थी। – Cengiz

+0

मेरे लिए ठीक काम करता है (सत्यापन सहित)। सुनिश्चित करें कि आप @PrepareForTest का उपयोग कर रहे हैं और यह भी सत्यापित करते हैं कि आपने लॉग फ़ैक्टरी की सही लॉग लुकअप विधि का मज़ाक उड़ाया है।उल्लेख के लिए –

5

अपने पहले सवाल का जवाब में, यह जगह के रूप में सरल किया जाना चाहिए:

when(LoggerFactory.getLogger(GoodbyeController.class)).thenReturn(loggerMock); 

साथ

when(LoggerFactory.getLogger(any(Class.class))).thenReturn(loggerMock); 
अपने दूसरे प्रश्न के बारे में

(और पहली साथ संभवतः puzzling व्यवहार), मैं लगता है कि समस्या यह है कि लॉगर स्थिर है।तो,

private static Logger logger = LoggerFactory.getLogger(GoodbyeController.class); 

जब वर्ग आरंभ नहीं हो जाता है, जब वस्तु instantiated है नहीं मार डाला जाता है। कभी-कभी यह लगभग एक ही समय में हो सकता है, इसलिए आप ठीक रहेगा, लेकिन इसकी गारंटी देना मुश्किल है। तो आप अपने मॉक को वापस करने के लिए LoggerFactory.getLogger सेट अप करते हैं, लेकिन लॉगर वैरिएबल पहले से ही आपके लॉक सेट अप होने तक वास्तविक लॉगर ऑब्जेक्ट के साथ सेट हो चुका है।

आप ReflectionTestUtils (मुझे नहीं पता कि यह स्थिर फ़ील्ड के साथ काम करता है) या किसी स्थिर क्षेत्र से एक आवृत्ति फ़ील्ड में इसे बदलकर लॉगजर को स्पष्ट रूप से सेट करने में सक्षम हो सकता है। किसी भी तरह से, आपको LoggerFactory.getLogger को नकल करने की आवश्यकता नहीं है क्योंकि आप सीधे नकली लॉगर उदाहरण इंजेक्शन देंगे।

7

कुछ देर से पार्टी के लिए देर से - मैं कुछ ऐसा कर रहा था और कुछ पॉइंटर्स की आवश्यकता थी और यहां समाप्त हो गया। कोई क्रेडिट नहीं ले रहा - मैंने ब्रिस से सभी कोड लिया लेकिन सेन्गीज़ की तुलना में "शून्य इंटरैक्शन" प्राप्त हुआ।

जेरिकिक्स एमडी जोसेफ लस्ट ने जो कहा था, उससे मार्गदर्शन का उपयोग करके मुझे लगता है कि मुझे पता है कि क्यों - मेरा क्षेत्र एक क्षेत्र के रूप में परीक्षण के तहत था और ब्रिस के विपरीत @ इससे पहले इसे नया बना दिया। फिर वास्तविक लॉगर नकली नहीं था लेकिन एक वास्तविक वर्ग में झिर्क के रूप में सूचित किया गया ...

मैं सामान्य रूप से परीक्षण के तहत अपने ऑब्जेक्ट के लिए ऐसा करता हूं ताकि प्रत्येक परीक्षण के लिए एक नई वस्तु प्राप्त हो सके। जब मैंने क्षेत्र को स्थानीय में स्थानांतरित कर दिया और परीक्षण में इसे नया लगा तो यह ठीक हो गया। हालांकि, अगर मैंने दूसरे परीक्षण की कोशिश की तो यह मेरे परीक्षण में नकली नहीं था, लेकिन पहले टेस्ट से नकली और मुझे फिर से शून्य इंटरैक्शन मिल गया।

जब मैं परीक्षण के अंतर्गत वस्तु में लकड़हारा @BeforeClass में नकली के निर्माण डाल हमेशा नकली है लेकिन इस के साथ समस्याओं के लिए नीचे नोट देखें ... परीक्षण

तहत

क्लास

import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class MyClassWithSomeLogging { private static final Logger LOG = LoggerFactory.getLogger(MyClassWithSomeLogging.class); public void doStuff(boolean b) { if(b) { LOG.info("true"); } else { LOG.info("false"); } } } 

टेस्ट

import org.junit.AfterClass; 
import org.junit.BeforeClass; 
import org.junit.Test; 
import org.junit.runner.RunWith; 
import org.powermock.core.classloader.annotations.PrepareForTest; 
import org.powermock.modules.junit4.PowerMockRunner; 
import org.slf4j.Logger; 
import org.slf4j.LoggerFactory; 

import static org.mockito.Mockito.*; 
import static org.powermock.api.mockito.PowerMockito.mock; 
import static org.powermock.api.mockito.PowerMockito.*; 
import static org.powermock.api.mockito.PowerMockito.when; 


@RunWith(PowerMockRunner.class) 
@PrepareForTest({LoggerFactory.class}) 
public class MyClassWithSomeLoggingTest { 

    private static Logger mockLOG; 

    @BeforeClass 
    public static void setup() { 
     mockStatic(LoggerFactory.class); 
     mockLOG = mock(Logger.class); 
     when(LoggerFactory.getLogger(any(Class.class))).thenReturn(mockLOG); 
    } 

    @Test 
    public void testIt() { 
     MyClassWithSomeLogging myClassWithSomeLogging = new MyClassWithSomeLogging(); 
     myClassWithSomeLogging.doStuff(true); 

     verify(mockLOG, times(1)).info("true"); 
    } 

    @Test 
    public void testIt2() { 
     MyClassWithSomeLogging myClassWithSomeLogging = new MyClassWithSomeLogging(); 
     myClassWithSomeLogging.doStuff(false); 

     verify(mockLOG, times(1)).info("false"); 
    } 

    @AfterClass 
    public static void verifyStatic() { 
     verify(mockLOG, times(1)).info("true"); 
     verify(mockLOG, times(1)).info("false"); 
     verify(mockLOG, times(2)).info(anyString()); 
    } 
} 

नोट

आप एक ही उम्मीद के साथ दो टेस्ट है, तो मैं @AfterClass में की पुष्टि के रूप में स्थिर पर आमंत्रण ऊपर खड़ी दिखती हैं करना था - verify(mockLOG, times(2)).info("true"); - के बजाय बार (1) दूसरे के रूप में प्रत्येक परीक्षा में परीक्षण कहने में असफल रहेगा कि इस पर 2 आमंत्रण। यह सुंदर पैंट है लेकिन मुझे आमंत्रण को साफ़ करने का कोई तरीका नहीं मिला। मैं जानना चाहता हूं कि कोई इस दौर के बारे में सोच सकता है ....

+1

मार्कस वेंडल के सुझाव ने रीसेट का उपयोग करने के लिए मेरे लिए काम किया है। –

2

मुझे लगता है कि आप Mockito.reset (mockLog) का उपयोग करके इनवॉशंस को रीसेट कर सकते हैं। आपको हर टेस्ट से पहले इसे कॉल करना चाहिए, इसलिए अंदर @ इससे पहले एक अच्छी जगह होगी।

0

स्पष्ट इंजेक्शन का उपयोग करें। कोई अन्य दृष्टिकोण आपको उदाहरण के लिए उसी JVM में समानांतर में परीक्षण चलाने की अनुमति नहीं देगा।

पैटर्न जो कुछ भी क्लासलोडर चौड़े जैसे स्थिर लॉग बाइंडर का उपयोग करते हैं या पर्यावरणीय सोचों के साथ गड़बड़ करते हैं जैसे कि logback.XML परीक्षण की बात आती है।

मेरे द्वारा उल्लिखित समांतर परीक्षणों पर विचार करें, या उस मामले पर विचार करें जहां आप घटक ए के लॉगिंग को रोकना चाहते हैं जिसका निर्माण एपीआई बी के पीछे छिपा हुआ है। यह बाद का मामला निपटने में आसान है यदि आप निर्भरता इंजेक्शन वाले लॉगरफैक्टरी से उपयोग कर रहे हैं शीर्ष, लेकिन अगर आप लॉगर इंजेक्ट नहीं करते हैं क्योंकि इस असेंबली में ILoggerFactory.getLogger पर कोई सीम नहीं है।

और यह यूनिट परीक्षण के बारे में सब कुछ नहीं है। कभी-कभी हम लॉगिंग उत्सर्जित करने के लिए एकीकरण परीक्षण चाहते हैं। कभी-कभी हम नहीं करते हैं। किसी के पास हम कुछ एकीकरण परीक्षण लॉगिंग को चुनिंदा दबाए जाने के लिए चाहते हैं, उदाहरण के लिए अपेक्षित त्रुटियों के लिए जो अन्यथा सीआई कंसोल और भ्रमित हो जाएंगे। सभी के लिए आसान अगर आप अपने मुख्य लाइन के ऊपर से ILoggerFactory इंजेक्षन (या जो भी di ढांचे आप उपयोग कर सकते हैं)

तो ...

या तो एक पत्रकार के रूप में सुझाव इंजेक्षन या ILoggerFactory इंजेक्शन लगाने की एक पद्धति को अपनाने। लॉगर की बजाय स्पष्ट ILoggerFactory इंजेक्शन द्वारा आप कई एक्सेस/अवरोध पैटर्न और समांतरता का समर्थन कर सकते हैं।

संबंधित मुद्दे