2016-02-22 2 views
36

मेरे कोटलिन जुनीट परीक्षणों में, मैं एम्बेडेड सर्वर शुरू/बंद करना चाहता हूं और अपने परीक्षणों में उनका उपयोग करना चाहता हूं।मैं कोटलिन में यूनिट टेस्ट संसाधनों का प्रबंधन कैसे करूं, जैसे डेटाबेस कनेक्शन शुरू करना/रोकना या एम्बेडेड लोचदार खोज सर्वर?

मैं अपने परीक्षण कक्षा में एक विधि पर JUnit @Before एनोटेशन उपयोग करने की कोशिश है और यह ठीक काम करता है, लेकिन यह सही व्यवहार के बाद से यह हर परीक्षण का मामला सिर्फ एक बार के बजाय चलाता नहीं है।

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

तो मैं अपने सभी परीक्षण मामलों के लिए केवल एक बार इस एम्बेडेड डेटाबेस को कैसे बना सकता हूं?

class MyTest { 
    @Before fun setup() { 
     // works in that it opens the database connection, but is wrong 
     // since this is per test case instead of being shared for all 
    } 

    @BeforeClass fun setupClass() { 
     // what I want to do instead, but results in error because 
     // this isn't a static method, and static keyword doesn't exist 
    } 

    var referenceToServer: ServerType // wrong because is not static either 

    ... 
} 

नोट:इस सवाल जानबूझकर लिखा है और लेखक (Self-Answered Questions) द्वारा उत्तर दिया, ताकि उत्तर आमतौर पर पूछे जाने वाले Kotlin विषयों अतः में मौजूद हैं।

+1

JUnit 5 कि उपयोग के मामले के लिए गैर स्थिर तरीकों का समर्थन कर सकते संक्षिप्त कर रहे हैं, को देखने के https://github.com/ जूनिट-टीम/जूनिट 5/अंक/41 9 # मुद्दा -267815529 और कोटलिन डेवलपर्स को इस तरह के सुधारों में रुचि रखने के लिए मेरी टिप्पणी +1 करने में संकोच न करें। –

उत्तर

59

आपकी इकाई परीक्षण कक्षा को आमतौर पर परीक्षण विधियों के समूह के लिए साझा संसाधन प्रबंधित करने के लिए कुछ चीजों की आवश्यकता होती है। और कोटलिन में आप @BeforeClass और @AfterClass परीक्षण कक्षा में नहीं, बल्कि companion object के साथ @JvmStatic annotation के साथ उपयोग कर सकते हैं।

एक परीक्षण वर्ग की संरचना लगेगा जैसे:

class MyTestClass { 
    companion object { 
     init { 
      // things that may need to be setup before companion class member variables are instantiated 
     } 

     // variables you initialize for the class just once: 
     val someClassVar = initializer() 

     // variables you initialize for the class later in the @BeforeClass method: 
     lateinit var someClassLateVar: SomeResource 

     @BeforeClass @JvmStatic fun setup() { 
      // things to execute once and keep around for the class 
     } 

     @AfterClass @JvmStatic fun teardown() { 
      // clean up after this class, leave nothing dirty behind 
     } 
    } 

    // variables you initialize per instance of the test class: 
    val someInstanceVar = initializer() 

    // variables you initialize per test case later in your @Before methods: 
    var lateinit someInstanceLateZVar: MyType 

    @Before fun prepareTest() { 
     // things to do before each test 
    } 

    @After fun cleanupTest() { 
     // things to do after each test 
    } 

    @Test fun testSomething() { 
     // an actual test case 
    } 

    @Test fun testSomethingElse() { 
     // another test case 
    } 

    // ...more test cases 
} 

ऊपर देखते हुए, आप के बारे में पढ़ा जाना चाहिए:

  • companion objects - जावा में क्लास वस्तु के समान है, लेकिन एक सिंगलटन प्रति वर्ग जो स्थिर नहीं है
  • @JvmStatic - एक एनोटेशन जो बाहरी वर्ग पर एक स्थिर विधि में एक साथी ऑब्जेक्ट विधि को बदलता है आर जावा इंटरॉप
  • lateinit - अनुमति देता है एक var संपत्ति बाद में प्रारंभ करने के लिए आप एक अच्छी तरह से परिभाषित जीवन चक्र
  • Delegates.notNull() है जब - एक संपत्ति है कि कम से कम एक बार पढ़ा जा रहा से पहले स्थापित किया जाना चाहिए के लिए lateinit के स्थान पर किया जा सकता है।

यहां कोटलिन के लिए परीक्षण कक्षाओं के पूर्ण उदाहरण हैं जो एम्बेडेड संसाधनों का प्रबंधन करते हैं।

पहली बार कॉपी किया गया है और Solr-Undertow tests से संशोधित किया गया है, और परीक्षण के मामलों को चलाने से पहले, एक सोलर-अंडरटेव सर्वर को कॉन्फ़िगर और प्रारंभ करता है। परीक्षण चलाने के बाद, यह परीक्षण द्वारा बनाई गई किसी भी अस्थायी फ़ाइलों को साफ़ करता है। यह सुनिश्चित करता है कि परीक्षण चर से पहले पर्यावरण चर और सिस्टम गुण सही हैं। परीक्षण मामलों के बीच यह किसी अस्थायी लोड किए गए सोलर कोर को उतार देता है। परीक्षण:

class TestServerWithPlugin { 
    companion object { 
     val workingDir = Paths.get("test-data/solr-standalone").toAbsolutePath() 
     val coreWithPluginDir = workingDir.resolve("plugin-test/collection1") 

     lateinit var server: Server 

     @BeforeClass @JvmStatic fun setup() { 
      assertTrue(coreWithPluginDir.exists(), "test core w/plugin does not exist $coreWithPluginDir") 

      // make sure no system properties are set that could interfere with test 
      resetEnvProxy() 
      cleanSysProps() 
      routeJbossLoggingToSlf4j() 
      cleanFiles() 

      val config = mapOf(...) 
      val configLoader = ServerConfigFromOverridesAndReference(workingDir, config) verifiedBy { loader -> 
       ... 
      } 

      assertNotNull(System.getProperty("solr.solr.home")) 

      server = Server(configLoader) 
      val (serverStarted, message) = server.run() 
      if (!serverStarted) { 
       fail("Server not started: '$message'") 
      } 
     } 

     @AfterClass @JvmStatic fun teardown() { 
      server.shutdown() 
      cleanFiles() 
      resetEnvProxy() 
      cleanSysProps() 
     } 

     private fun cleanSysProps() { ... } 

     private fun cleanFiles() { 
      // don't leave any test files behind 
      coreWithPluginDir.resolve("data").deleteRecursively() 
      Files.deleteIfExists(coreWithPluginDir.resolve("core.properties")) 
      Files.deleteIfExists(coreWithPluginDir.resolve("core.properties.unloaded")) 
     } 
    } 

    val adminClient: SolrClient = HttpSolrClient("http://localhost:8983/solr/") 

    @Before fun prepareTest() { 
     // anything before each test? 
    } 

    @After fun cleanupTest() { 
     // make sure test cores do not bleed over between test cases 
     unloadCoreIfExists("tempCollection1") 
     unloadCoreIfExists("tempCollection2") 
     unloadCoreIfExists("tempCollection3") 
    } 

    private fun unloadCoreIfExists(name: String) { ... } 

    @Test 
    fun testServerLoadsPlugin() { 
     println("Loading core 'withplugin' from dir ${coreWithPluginDir.toString()}") 
     val response = CoreAdminRequest.createCore("tempCollection1", coreWithPluginDir.toString(), adminClient) 
     assertEquals(0, response.status) 
    } 

    // ... other test cases 
} 

और एक और शुरू करने एडब्ल्यूएस DynamoDB एक एम्बेडेड डेटाबेस के रूप में स्थानीय (नकल की और Running AWS DynamoDB-local embedded से थोड़ा संशोधित)।यह परीक्षण java.library.path को किसी और चीज से पहले या स्थानीय डायनेमोडीबी (बाइनरी लाइब्रेरीज़ के साथ स्क्लाइट का उपयोग करके) चलाने से पहले हैक नहीं करेगा। फिर यह सभी परीक्षण वर्गों के लिए साझा करने के लिए सर्वर शुरू करता है, और परीक्षणों के बीच अस्थायी डेटा को साफ़ करता है। परीक्षण:

class TestAccountManager { 
    companion object { 
     init { 
      // we need to control the "java.library.path" or sqlite cannot find its libraries 
      val dynLibPath = File("./src/test/dynlib/").absoluteFile 
      System.setProperty("java.library.path", dynLibPath.toString()); 

      // TEST HACK: if we kill this value in the System classloader, it will be 
      // recreated on next access allowing java.library.path to be reset 
      val fieldSysPath = ClassLoader::class.java.getDeclaredField("sys_paths") 
      fieldSysPath.setAccessible(true) 
      fieldSysPath.set(null, null) 

      // ensure logging always goes through Slf4j 
      System.setProperty("org.eclipse.jetty.util.log.class", "org.eclipse.jetty.util.log.Slf4jLog") 
     } 

     private val localDbPort = 19444 

     private lateinit var localDb: DynamoDBProxyServer 
     private lateinit var dbClient: AmazonDynamoDBClient 
     private lateinit var dynamo: DynamoDB 

     @BeforeClass @JvmStatic fun setup() { 
      // do not use ServerRunner, it is evil and doesn't set the port correctly, also 
      // it resets logging to be off. 
      localDb = DynamoDBProxyServer(localDbPort, LocalDynamoDBServerHandler(
        LocalDynamoDBRequestHandler(0, true, null, true, true), null) 
      ) 
      localDb.start() 

      // fake credentials are required even though ignored 
      val auth = BasicAWSCredentials("fakeKey", "fakeSecret") 
      dbClient = AmazonDynamoDBClient(auth) initializedWith { 
       signerRegionOverride = "us-east-1" 
       setEndpoint("http://localhost:$localDbPort") 
      } 
      dynamo = DynamoDB(dbClient) 

      // create the tables once 
      AccountManagerSchema.createTables(dbClient) 

      // for debugging reference 
      dynamo.listTables().forEach { table -> 
       println(table.tableName) 
      } 
     } 

     @AfterClass @JvmStatic fun teardown() { 
      dbClient.shutdown() 
      localDb.stop() 
     } 
    } 

    val jsonMapper = jacksonObjectMapper() 
    val dynamoMapper: DynamoDBMapper = DynamoDBMapper(dbClient) 

    @Before fun prepareTest() { 
     // insert commonly used test data 
     setupStaticBillingData(dbClient) 
    } 

    @After fun cleanupTest() { 
     // delete anything that shouldn't survive any test case 
     deleteAllInTable<Account>() 
     deleteAllInTable<Organization>() 
     deleteAllInTable<Billing>() 
    } 

    private inline fun <reified T: Any> deleteAllInTable() { ... } 

    @Test fun testAccountJsonRoundTrip() { 
     val acct = Account("123", ...) 
     dynamoMapper.save(acct) 

     val item = dynamo.getTable("Accounts").getItem("id", "123") 
     val acctReadJson = jsonMapper.readValue<Account>(item.toJSON()) 
     assertEquals(acct, acctReadJson) 
    } 

    // ...more test cases 

} 

नोट: उदाहरण के कुछ हिस्सों के साथ ...

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