मॉकिटो के साथ स्थैतिक तरीकों का मजाक बनाना


371

मैंने java.sql.Connectionवस्तुओं का उत्पादन करने के लिए एक कारखाना लिखा है :

public class MySQLDatabaseConnectionFactory implements DatabaseConnectionFactory {

    @Override public Connection getConnection() {
        try {
            return DriverManager.getConnection(...);
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }
}

मैं पास किए गए मापदंडों को मान्य करना चाहता हूं DriverManager.getConnection, लेकिन मुझे नहीं पता कि स्थैतिक विधि का मजाक कैसे उड़ाया जाता है। मैं अपने परीक्षण मामलों के लिए JUnit 4 और Mockito का उपयोग कर रहा हूं। क्या इस विशिष्ट उपयोग-मामले का मजाक / सत्यापन करने का एक अच्छा तरीका है?


1
क्या इससे मदद मिलेगी? stackoverflow.com/questions/19464975/…
sasankad

5
आप mockito साथ नहीं कर सकते हैं desing द्वारा :)
MariuszS

25
@ मर्सिज़ यह डिज़ाइन द्वारा नहीं है कि मॉकिटो (या ईज़ीमॉक या जेमॉक) मॉकिंग staticविधियों का समर्थन नहीं करता है , लेकिन दुर्घटना से । यह सीमा (मॉकिंग finalक्लासेस / मेथड्स, या new-ड ऑब्जेक्ट्स के लिए कोई सपोर्ट न होने के साथ ) मॉकिंग को लागू करने के लिए नियोजित दृष्टिकोण का एक स्वाभाविक (लेकिन अनपेक्षित) परिणाम है, जहां नई कक्षाएं गतिशील रूप से बनाई जाती हैं जो मॉक किए जाने वाले प्रकार को लागू / विस्तारित करती हैं; अन्य मॉकिंग लाइब्रेरी अन्य दृष्टिकोणों का उपयोग करती हैं जो इन सीमाओं से बचती हैं। यह .NET की दुनिया में भी हुआ।
रोजरियो

2
@ Rogério स्पष्टीकरण के लिए धन्यवाद। github.com/mockito/mockito/wiki/FAQ क्या मैं स्थैतिक तरीकों का मजाक उड़ा सकता हूं? Mockito स्थैतिक, प्रक्रियात्मक कोड पर ऑब्जेक्ट ओरिएंटेशन और निर्भरता इंजेक्शन पसंद करता है जो समझने और बदलने के लिए कठिन है। इस सीमा के पीछे भी कुछ डिजाइन है :)
MariuszS

17
@ मर्सिडीज मैंने पढ़ा कि उपकरण को स्वीकार करने के बजाय वैध उपयोग के मामलों को खारिज करने के प्रयास के रूप में ऐसी सीमाएँ हैं जिन्हें आसानी से हटाया नहीं जा सकता है, और बिना किसी उचित औचित्य प्रदान किए। बीटीडब्ल्यू, यहां संदर्भों के साथ विपरीत दृष्टिकोण के लिए ऐसी चर्चा है
रोजरियो

जवाबों:


350

मॉकिटो के शीर्ष पर पॉवरमॉकिटो का उपयोग करें ।

उदाहरण कोड:

@RunWith(PowerMockRunner.class)
@PrepareForTest(DriverManager.class)
public class Mocker {

    @Test
    public void shouldVerifyParameters() throws Exception {

        //given
        PowerMockito.mockStatic(DriverManager.class);
        BDDMockito.given(DriverManager.getConnection(...)).willReturn(...);

        //when
        sut.execute(); // System Under Test (sut)

        //then
        PowerMockito.verifyStatic();
        DriverManager.getConnection(...);

    }

अधिक जानकारी:


4
जबकि यह सिद्धांत में काम करता है, अभ्यास में कठिन समय ...
Naftuli Kay

38
दुर्भाग्य से इस का भारी नुकसान PowerMockRunner के लिए की जरूरत है।
इनकेंटी

18
sut.execute ()? माध्यम?
तेजज

4
सिस्टम अंडर टेस्ट, वह क्लास जिसे ड्राइवरमैन की नकली की आवश्यकता होती है। kaczanowscy.pl/tomek/2011-01/testing-basics-sut-and-docs
MariuszS

8
FYI करें, यदि आप पहले से ही JUnit4 का उपयोग कर रहे हैं तो आप कर सकते हैं @RunWith(PowerMockRunner.class)और उसके नीचे @PowerMockRunnerDelegate(JUnit4.class)
EM-Creations

71

स्थिर तरीकों को चकमा देने के लिए विशिष्ट रणनीति जिसे आपके पास उपयोग करने से बचने का कोई तरीका नहीं है, लपेटी गई वस्तुओं को बनाने और इसके बजाय आवरण वस्तुओं का उपयोग करने से है।

आवरण की वस्तुएं वास्तविक स्थिर वर्गों के लिए मुखर हो जाती हैं, और आप उन का परीक्षण नहीं करते हैं।

एक आवरण वस्तु कुछ इस तरह हो सकती है

public class Slf4jMdcWrapper {
    public static final Slf4jMdcWrapper SINGLETON = new Slf4jMdcWrapper();

    public String myApisToTheSaticMethodsInSlf4jMdcStaticUtilityClass() {
        return MDC.getWhateverIWant();
    }
}

अंत में, परीक्षण के तहत आपकी कक्षा इस सिंगलटन ऑब्जेक्ट का उपयोग कर सकती है, उदाहरण के लिए, वास्तविक जीवन के उपयोग के लिए एक डिफ़ॉल्ट निर्माणकर्ता:

public class SomeClassUnderTest {
    final Slf4jMdcWrapper myMockableObject;

    /** constructor used by CDI or whatever real life use case */
    public myClassUnderTestContructor() {
        this.myMockableObject = Slf4jMdcWrapper.SINGLETON;
    }

    /** constructor used in tests*/
    myClassUnderTestContructor(Slf4jMdcWrapper myMock) {
        this.myMockableObject = myMock;
    }
}

और यहां आपके पास एक वर्ग है जिसे आसानी से परीक्षण किया जा सकता है, क्योंकि आप सीधे स्थिर विधियों के साथ एक वर्ग का उपयोग नहीं करते हैं।

यदि आप CDI का उपयोग कर रहे हैं और @Inject एनोटेशन का उपयोग कर सकते हैं तो यह और भी आसान है। बस अपने रैपर बीन को बनाएं @ApplicationScoped, उस चीज़ को सहयोगी के रूप में इंजेक्ट करें (आपको परीक्षण के लिए गन्दे निर्माणकर्ताओं की भी आवश्यकता नहीं है), और मॉकिंग के साथ आगे बढ़ें।


3
मैंने जावा 8 "मिक्सिन" इंटरफेस को स्वचालित रूप से उत्पन्न करने के लिए एक उपकरण बनाया है जो स्थिर कॉल को लपेटता है: github.com/aro-tech/interface-it उत्पन्न मिश्रण किसी भी अन्य इंटरफ़ेस की तरह मॉक किया जा सकता है, या यदि आपकी कक्षा "परीक्षण" के अंतर्गत आती है इंटरफ़ेस आप परीक्षण के लिए एक उपवर्ग में इसके किसी भी तरीके को ओवरराइड कर सकते हैं।
aro_tech

25

मेरा मुद्दा भी ऐसा ही था। मॉकस्टैटिक के@PrepareForTest(TheClassThatContainsStaticMethod.class) लिए पॉवरमॉक के दस्तावेज के अनुसार, स्वीकृत उत्तर ने मेरे लिए काम नहीं किया: जब तक मैंने बदलाव नहीं किया ।

और मुझे इस्तेमाल नहीं करना है BDDMockito

मेरी कक्षा:

public class SmokeRouteBuilder {
    public static String smokeMessageId() {
        try {
            return InetAddress.getLocalHost().getHostAddress();
        } catch (UnknownHostException e) {
            log.error("Exception occurred while fetching localhost address", e);
            return UUID.randomUUID().toString();
        }
    }
}

मेरा परीक्षण वर्ग:

@RunWith(PowerMockRunner.class)
@PrepareForTest(SmokeRouteBuilder.class)
public class SmokeRouteBuilderTest {
    @Test
    public void testSmokeMessageId_exception() throws UnknownHostException {
        UUID id = UUID.randomUUID();

        mockStatic(InetAddress.class);
        mockStatic(UUID.class);
        when(InetAddress.getLocalHost()).thenThrow(UnknownHostException.class);
        when(UUID.randomUUID()).thenReturn(id);

        assertEquals(id.toString(), SmokeRouteBuilder.smokeMessageId());
    }
}

यह पता लगाने में सक्षम नहीं है। मॉकस्टैटिक और? वर्तमान में JUnit 4 के साथ
टेड्डी

PowerMock.mockStatic और Mockito.when काम करने के लिए प्रतीत नहीं होता है।
टेडी

बाद में इसे देखने वाले किसी भी व्यक्ति के लिए, मुझे PowerMockito.mockStatic (StaticClass.class) लिखना था;
विचारक

आपको powermock-api-mockito maven arterfact को शामिल करना होगा।
पीटरसन

23

जैसा कि पहले उल्लेख किया गया है कि आप मॉकिटो के साथ स्थिर तरीकों का मजाक नहीं उड़ा सकते हैं।

यदि आपके परीक्षण ढांचे को बदलना एक विकल्प नहीं है, तो आप निम्नलिखित कार्य कर सकते हैं:

DriverManager के लिए एक इंटरफ़ेस बनाएँ, इस इंटरफ़ेस का मज़ाक करें, इसे किसी प्रकार की निर्भरता इंजेक्शन के माध्यम से इंजेक्ट करें और उस नकली पर सत्यापित करें।


7

अवलोकन: जब आप स्थैतिक इकाई के भीतर स्थैतिक विधि कहते हैं, तो आपको @PrepareForTest में कक्षा बदलने की आवश्यकता है।

उदाहरण के लिए:

securityAlgo = MessageDigest.getInstance(SECURITY_ALGORITHM);

उपरोक्त कोड के लिए यदि आपको MessageDigest वर्ग का उपयोग करने की आवश्यकता है, तो उपयोग करें

@PrepareForTest(MessageDigest.class)

जबकि अगर आपके पास नीचे जैसा कुछ है:

public class CustomObjectRule {

    object = DatatypeConverter.printHexBinary(MessageDigest.getInstance(SECURITY_ALGORITHM)
             .digest(message.getBytes(ENCODING)));

}

फिर, आपको उस वर्ग को तैयार करना होगा, जिसमें यह कोड रहता है।

@PrepareForTest(CustomObjectRule.class)

और फिर विधि का मज़ाक उड़ाएँ:

PowerMockito.mockStatic(MessageDigest.class);
PowerMockito.when(MessageDigest.getInstance(Mockito.anyString()))
      .thenThrow(new RuntimeException());

मैं दीवार के खिलाफ मेरे सिर को पीटने की कोशिश कर रहा था ताकि पता चल सके कि मेरा स्टैटिक क्लास मॉकिंग क्यों नहीं कर रहा था। आप इंटरव्यू पर सभी ट्यूटोरियल्स में सोचेंगे, एक तो नंगे-हड्डियों के उपयोग के मामले में अधिक से अधिक हो जाएगा।
SoftwareSavant

6

आप इसे थोड़े से रिफ्लेक्टरिंग के साथ कर सकते हैं:

public class MySQLDatabaseConnectionFactory implements DatabaseConnectionFactory {

    @Override public Connection getConnection() {
        try {
            return _getConnection(...some params...);
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }

    //method to forward parameters, enabling mocking, extension, etc
    Connection _getConnection(...some params...) throws SQLException {
        return DriverManager.getConnection(...some params...);
    }
}

फिर आप MySQLDatabaseConnectionFactoryएक नकली कनेक्शन वापस करने के लिए अपनी कक्षा का विस्तार कर सकते हैं , मापदंडों पर दावे कर सकते हैं, आदि।

विस्तारित वर्ग परीक्षण के मामले में रह सकता है, यदि यह उसी पैकेज में स्थित है (जो मैं आपको करने के लिए प्रोत्साहित करता हूं)

public class MockedConnectionFactory extends MySQLDatabaseConnectionFactory {

    Connection _getConnection(...some params...) throws SQLException {
        if (some param != something) throw new InvalidParameterException();

        //consider mocking some methods with when(yourMock.something()).thenReturn(value)
        return Mockito.mock(Connection.class);
    }
}

6

स्थैतिक विधि का मजाक उड़ाने के लिए आपको एक पावरमॉक लुक का उपयोग करना चाहिए: https://github.com/powermock/powermock/wiki/MockStatic । मॉकिटो यह कार्यक्षमता प्रदान नहीं करता है

आप मॉकिटो के बारे में एक अच्छा लेख पढ़ सकते हैं: http://refcardz.dzone.com/refcardz/mockito


2
कृपया किसी वेबसाइट से लिंक न करें। उत्तर में वास्तविक उपयोग योग्य उत्तर शामिल होने चाहिए। यदि साइट नीचे जाती है या बदल जाती है, तो उत्तर अब मान्य नहीं है।
the_new_mr

6

मॉकिटो स्थैतिक विधियों पर कब्जा नहीं कर सकता है, लेकिन मॉकिटो 2.14.0 के बाद से आप स्थैतिक विधियों के मंगलाचरण उदाहरण बनाकर इसे अनुकरण कर सकते हैं।

उदाहरण ( उनके परीक्षणों से निकाला गया ):

public class StaticMockingExperimentTest extends TestBase {

    Foo mock = Mockito.mock(Foo.class);
    MockHandler handler = Mockito.mockingDetails(mock).getMockHandler();
    Method staticMethod;
    InvocationFactory.RealMethodBehavior realMethod = new InvocationFactory.RealMethodBehavior() {
        @Override
        public Object call() throws Throwable {
            return null;
        }
    };

    @Before
    public void before() throws Throwable {
        staticMethod = Foo.class.getDeclaredMethod("staticMethod", String.class);
    }

    @Test
    public void verify_static_method() throws Throwable {
        //register staticMethod call on mock
        Invocation invocation = Mockito.framework().getInvocationFactory().createInvocation(mock, withSettings().build(Foo.class), staticMethod, realMethod,
                "some arg");
        handler.handle(invocation);

        //verify staticMethod on mock
        //Mockito cannot capture static methods so we will simulate this scenario in 3 steps:
        //1. Call standard 'verify' method. Internally, it will add verificationMode to the thread local state.
        //  Effectively, we indicate to Mockito that right now we are about to verify a method call on this mock.
        verify(mock);
        //2. Create the invocation instance using the new public API
        //  Mockito cannot capture static methods but we can create an invocation instance of that static invocation
        Invocation verification = Mockito.framework().getInvocationFactory().createInvocation(mock, withSettings().build(Foo.class), staticMethod, realMethod,
                "some arg");
        //3. Make Mockito handle the static method invocation
        //  Mockito will find verification mode in thread local state and will try verify the invocation
        handler.handle(verification);

        //verify zero times, method with different argument
        verify(mock, times(0));
        Invocation differentArg = Mockito.framework().getInvocationFactory().createInvocation(mock, withSettings().build(Foo.class), staticMethod, realMethod,
                "different arg");
        handler.handle(differentArg);
    }

    @Test
    public void stubbing_static_method() throws Throwable {
        //register staticMethod call on mock
        Invocation invocation = Mockito.framework().getInvocationFactory().createInvocation(mock, withSettings().build(Foo.class), staticMethod, realMethod,
                "foo");
        handler.handle(invocation);

        //register stubbing
        when(null).thenReturn("hey");

        //validate stubbed return value
        assertEquals("hey", handler.handle(invocation));
        assertEquals("hey", handler.handle(invocation));

        //default null value is returned if invoked with different argument
        Invocation differentArg = Mockito.framework().getInvocationFactory().createInvocation(mock, withSettings().build(Foo.class), staticMethod, realMethod,
                "different arg");
        assertEquals(null, handler.handle(differentArg));
    }

    static class Foo {

        private final String arg;

        public Foo(String arg) {
            this.arg = arg;
        }

        public static String staticMethod(String arg) {
            return "";
        }

        @Override
        public String toString() {
            return "foo:" + arg;
        }
    }
}

उनका लक्ष्य सीधे स्थैतिक मॉकिंग का समर्थन करना नहीं है , बल्कि अपने सार्वजनिक एपीआई में सुधार करना है ताकि पॉवर्मॉकिटो जैसे अन्य पुस्तकालयों को आंतरिक एपीआई पर निर्भर न होना पड़े या सीधे कुछ मॉकिटो कोड को डुप्लिकेट करना पड़े। ( स्रोत )

डिस्क्लेमर: मॉकिटो टीम का मानना ​​है कि नरक की सड़क स्थैतिक तरीकों से बनाई गई है। हालांकि, मॉकिटो का काम आपके कोड को स्टैटिक तरीकों से बचाना नहीं है। यदि आप अपनी टीम को स्थैतिक मज़ाक करना पसंद नहीं करते हैं, तो अपने संगठन में पॉवर्मॉकिटो का उपयोग करना बंद कर दें। मॉकिटो को एक टूलकिट के रूप में विकसित करने की जरूरत है कि जावा परीक्षण कैसे लिखा जाना चाहिए (उदाहरण के लिए मॉक स्टैटिक्स नहीं)। हालांकि, मॉकिटो हठधर्मिता नहीं है। हम स्थैतिक मॉकिंग जैसे अनियंत्रित उपयोग के मामलों को रोकना नहीं चाहते हैं। यह सिर्फ हमारा काम नहीं है।



1

चूंकि यह विधि स्थिर है, इसलिए आपके पास पहले से ही इसका उपयोग करने के लिए आवश्यक सब कुछ है, इसलिए यह मॉकिंग के उद्देश्य को हरा देता है। स्थैतिक तरीकों का मजाक उड़ाना एक बुरा अभ्यास माना जाता है।

यदि आप ऐसा करने की कोशिश करते हैं, तो इसका मतलब है कि परीक्षण करने के तरीके के साथ कुछ गड़बड़ है।

बेशक आप पावरमॉकिटो या किसी भी अन्य ढांचे का उपयोग कर सकते हैं जो ऐसा करने में सक्षम है, लेकिन अपने दृष्टिकोण पर पुनर्विचार करने का प्रयास करें।

उदाहरण के लिए: वस्तुओं को मोक करने / प्रदान करने की कोशिश करें, जो कि स्थैतिक विधि इसके बजाय खपत करती है।


0

JMockit ढांचे का उपयोग करें । इसने मेरे लिए काम किया। आपको DBConenction.getConnection () विधि को मॉक करने के लिए कथन लिखने की आवश्यकता नहीं है। बस नीचे दिया गया कोड पर्याप्त है।

@ नीचे नीचे नकली है। नकली पैकेज

Connection jdbcConnection = Mockito.mock(Connection.class);

MockUp<DBConnection> mockUp = new MockUp<DBConnection>() {

            DBConnection singleton = new DBConnection();

            @Mock
            public DBConnection getInstance() { 
                return singleton;
            }

            @Mock
            public Connection getConnection() {
                return jdbcConnection;
            }
         };
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.