কোটলিনে আমি ইউনিট পরীক্ষার সংস্থানগুলি কীভাবে পরিচালনা করব যেমন একটি ডাটাবেস সংযোগ শুরু করা / থামানো বা এমবেডেড ইলাস্টিক অনুসন্ধান সার্ভার?


94

আমার কোটলিন 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

    ...
}

দ্রষ্টব্য: এই প্রশ্নটি লেখক ইচ্ছাকৃতভাবে লিখেছেন এবং উত্তর দিয়েছেন ( স্ব-উত্তরযুক্ত প্রশ্ন ), যাতে সাধারণভাবে জিজ্ঞাসিত কোটলিন বিষয়গুলির উত্তরগুলি এসওতে উপস্থিত থাকে।


4
জুনেট 5 ব্যবহারের ক্ষেত্রে অ স্থিতিশীল পদ্ধতিগুলিকে সমর্থন করতে পারে, github.com/junit-team/ জুনit5/issues/419#issuecomment-267815529 দেখুন এবং কোটলিন বিকাশকারীরা এই ধরনের উন্নতিতে আগ্রহী তা দেখানোর জন্য আমার মন্তব্যটি +1 নির্দ্বিধায় অনুভব করতে পারেন।
সেবাস্টিয়ান দেল্যুজ

উত্তর:


156

আপনার ইউনিট পরীক্ষা ক্লাসে সাধারণত পরীক্ষা পদ্ধতির একটি গোষ্ঠীর জন্য ভাগ করা সংস্থান পরিচালনার জন্য কয়েকটি জিনিস প্রয়োজন। আর Kotlin মধ্যে আপনি ব্যবহার করতে পারেন @BeforeClassএবং @AfterClassনা পরীক্ষা ক্লাসে, বরং তার মধ্যে সহচর বস্তুর সঙ্গে বরাবর @JvmStaticটীকা

একটি পরীক্ষার শ্রেণীর কাঠামোটি দেখতে এরকম হবে:

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
}  

উপরেরটি দেওয়া, আপনার সম্পর্কে পড়া উচিত:

  • সহচর অবজেক্টস - জাভাতে ক্লাস অবজেক্টের সমান, তবে ক্লাসে একক সিঙ্গলটন যা স্থির নয়
  • @JvmStatic - এমন একটি টীকা যা জাভা ইন্টারপের জন্য বাইরের শ্রেণিতে কোনও সহকর্মী অবজেক্ট পদ্ধতিটিকে স্থির পদ্ধতিতে রূপান্তর করে
  • lateinit- varআপনার যখন একটি সংজ্ঞায়িত জীবনচক্র থাকে তখন কোনও সম্পত্তি প্রাথমিকভাবে অনুমতি দেয়
  • Delegates.notNull()- এমন lateinitকোনও সম্পত্তির পরিবর্তে ব্যবহার করা যেতে পারে যা পড়ার আগে কমপক্ষে একবার সেট করা উচিত।

এখানে কোটলিনের পরীক্ষার শ্রেণীর পূর্ণ উদাহরণ যা এম্বেড থাকা সংস্থানগুলি পরিচালনা করে।

প্রথমটি সোলার-আন্ডারটও পরীক্ষাগুলি থেকে অনুলিপি করা হয় এবং সংশোধন করা হয় এবং পরীক্ষার কেসগুলি চালানোর আগে সোলার-আন্ডারটওয় সার্ভারটি কনফিগার করে এবং শুরু করে। পরীক্ষা চালানোর পরে, এটি পরীক্ষাগুলির দ্বারা তৈরি যে কোনও অস্থায়ী ফাইলগুলি সাফ করে। এটি পরীক্ষাগুলি চালুর আগে পরিবেশের ভেরিয়েবল এবং সিস্টেমের বৈশিষ্ট্যগুলি সঠিক কিনা তাও নিশ্চিত করে। পরীক্ষার ক্ষেত্রে এটি কোনও অস্থায়ী লোড হওয়া সোলার কোরগুলি আনলোড করে। পরীক্ষা:

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
}

এবং এম্বেডেড ডাটাবেস হিসাবে আর একটি ডাব্লুএস ডায়নামোডিবি স্থানীয় সূচনা ( অ্যাডাব্লুএস ডায়নামোডিবি-স্থানীয় এম্বেডেড থেকে চালানো থেকে কিছুটা অনুলিপি এবং সংশোধিত )। java.library.pathঅন্য কিছু হওয়ার আগে বা স্থানীয় ডায়নামোডিবি (বাইনারি লাইব্রেরি সহ স্ক্লাইট ব্যবহার করে) চালাতে না পারার আগে এই পরীক্ষাটি অবশ্যই হ্যাক করতে হবে। তারপরে সমস্ত পরীক্ষার ক্লাসে ভাগ করার জন্য এটি একটি সার্ভার শুরু করে এবং পরীক্ষার মধ্যে অস্থায়ী ডেটা পরিষ্কার করে cle পরীক্ষা:

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

}

দ্রষ্টব্য: উদাহরণগুলির কয়েকটি অংশ সংক্ষেপে বর্ণিত...


0

পরীক্ষাগুলিতে কলব্যাকের আগে / পরে রিসোর্সগুলি পরিচালনা করার পক্ষে অবশ্যই এর কার্যকারিতা রয়েছে:

  • টেস্টগুলি হ'ল "পারমাণবিক"। একটি পরীক্ষা সমস্ত কলব্যাক সহ পুরো জিনিস হিসাবে কার্যকর করে কেউ পরীক্ষার আগে কোনও নির্ভরতা পরিষেবা চালিত করতে এবং এটি শেষ করার পরে এটি বন্ধ করে দিতে ভুলবে না। যদি সঠিকভাবে করা হয় তবে মৃত্যুদণ্ড কার্যকর করা কলব্যাকগুলি যে কোনও পরিবেশে কাজ করবে।
  • টেস্টগুলি স্বয়ংসম্পূর্ণ। কোনও বাহ্যিক ডেটা বা সেটআপ পর্যায় নেই, সমস্ত কিছু কয়েকটি পরীক্ষার ক্লাসের মধ্যে রয়েছে।

এটির কিছুটা কনসও রয়েছে। এর মধ্যে একটি গুরুত্বপূর্ণ হ'ল এটি কোডটিকে দূষিত করে এবং কোডকে একক দায়িত্বের নীতি লঙ্ঘন করে। টেস্টগুলি এখন কেবল কিছু পরীক্ষা করে না, একটি হেভিওয়েট সূচনা এবং সংস্থান পরিচালনা করে। এটি কিছু ক্ষেত্রে ঠিকঠাক হতে পারে (যেমন একটি কনফিগার করাObjectMapper ) তবে java.library.pathঅন্য প্রক্রিয়াগুলি (বা প্রক্রিয়াভুক্ত এম্বেডড ডাটাবেসগুলি) সংশোধন বা তৈরি করা এত নির্দোষ নয়।

12factor.net দ্বারা বর্ণিত যেমন "ইঞ্জেকশন" এর জন্য আপনার পরীক্ষার যোগ্যতার জন্য এই পরিষেবাগুলিকে নির্ভরতা হিসাবে বিবেচনা করবেন না ।

এইভাবে আপনি পরীক্ষা কোডের বাইরে কোথাও নির্ভরতা পরিষেবাগুলি শুরু এবং সূচনা করেন।

আজকাল ভার্চুয়ালাইজেশন এবং ধারকগুলি প্রায় সর্বত্র এবং বেশিরভাগ বিকাশকারীর মেশিনগুলি ডকার চালাতে সক্ষম হয়। এবং বেশিরভাগ অ্যাপ্লিকেশনটির একটি ডকারাইজড সংস্করণ রয়েছে: ইলাস্টিকসার্ক , ডায়নামোডিবি , পোস্টগ্রিস এসকিউএল এবং আরও অনেক কিছু। আপনার পরীক্ষাগুলির জন্য বাহ্যিক পরিষেবার জন্য ডকার হ'ল একটি নিখুঁত সমাধান।

  • এটি এমন কোনও স্ক্রিপ্ট হতে পারে যা চালকরা যখন চালকরা পরীক্ষা চালাতে চান প্রত্যেকবার বিকাশকারী তাকে ম্যানুয়ালি চালিত করে।
  • এটি বিল্ড টুল দ্বারা চালিত কোনও কাজ হতে পারে (যেমন গ্রেডলের নির্ভরতা নির্ধারণের জন্য দুর্দান্ত dependsOnএবং finalizedByডিএসএল রয়েছে)। অবশ্যই একটি টাস্ক একই স্ক্রিপ্টটি কার্যকর করতে পারে যা বিকাশকারী শেল-আউট / প্রসেস এক্সিকিউটগুলি ব্যবহার করে ম্যানুয়ালি কার্যকর করে।
  • এটি টেস্ট কার্যকর করার আগে আইডিই দ্বারা চালিত কোনও কাজ হতে পারে । আবার এটি একই স্ক্রিপ্ট ব্যবহার করতে পারে।
  • বেশিরভাগ সিআই / সিডি সরবরাহকারীদের "পরিষেবা" ধারণা রয়েছে - একটি বাহ্যিক নির্ভরতা (প্রক্রিয়া) যা আপনার বিল্ডের সমান্তরালে চলে এবং এটির সাধারণ এসডিকে / সংযোজক / এপিআই এর মাধ্যমে অ্যাক্সেস করা যায়: গিটলব , ট্র্যাভিস , বিটবকেট , অ্যাপভিয়ার , সেমফোর ,…

এই পদ্ধতির:

  • আরম্ভের যুক্তি থেকে আপনার পরীক্ষার কোডকে মুক্ত করে। আপনার পরীক্ষাগুলি কেবল পরীক্ষা করবে এবং আরও কিছু করবে না।
  • কোড এবং ডেটা ডিকোলস করে। একটি নতুন পরীক্ষার কেস যুক্ত করা এখন নেটিভ টুলসেট সহ নির্ভরতা পরিষেবাগুলিতে নতুন ডেটা যুক্ত করে করা যেতে পারে। অর্থাত্ এসকিউএল ডাটাবেসের জন্য আপনি এসকিউএল ব্যবহার করবেন, অ্যামাজন ডায়নামোডিবি-র জন্য আপনি টেবিলগুলি তৈরি করতে এবং আইটেমগুলি রাখার জন্য সিএমআই ব্যবহার করবেন।
  • একটি প্রোডাকশন কোডের কাছাকাছি, যেখানে আপনার "মূল" অ্যাপ্লিকেশন শুরু হওয়ার সাথে আপনি অবশ্যই সে পরিষেবাগুলি শুরু করবেন না।

অবশ্যই, এর ত্রুটি রয়েছে (মূলত, আমি যে বিবৃতিগুলি থেকে শুরু করেছি):

  • টেস্টগুলি আরও "পারমাণবিক" হয় না। নির্ভরতা পরিষেবা কোনওভাবে আগে পরীক্ষার সম্পাদন শুরু করা উচিত। এটি শুরু করার উপায়টি বিভিন্ন পরিবেশে ভিন্ন হতে পারে: বিকাশকারীর মেশিন বা সিআই, আইডিই বা বিল্ড সরঞ্জাম সি এল এল।
  • টেস্টগুলি স্বয়ংসম্পূর্ণ হয় না। এখন আপনার বীজ ডেটা এমনকি একটি চিত্রের মধ্যে প্যাক করা যেতে পারে, সুতরাং এটি পরিবর্তন করার জন্য একটি ভিন্ন প্রকল্প পুনর্নির্মাণের প্রয়োজন হতে পারে।
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.