Как управлять ресурсами модульного тестирования в Kotlin, такими как запуск/остановка подключения к базе данных или встроенного сервера elasticsearch?
в моих тестах Kotlin JUnit я хочу запускать / останавливать встроенные серверы и использовать их в своих тестах.
Я пробовал использовать 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
...
}
Примечание:этот вопрос намеренно написан и на него отвечает автор (Вопросы С Самостоятельным Ответом), так что ответы на часто задаваемые темы Котлин присутствуют в ТАК.
1 ответ:
ваш класс модульного теста обычно нуждается в нескольких вещах для управления общим ресурсом для группы методов тестирования. А в Котлине вы можете использовать
@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 }
учитывая вышесказанное, вы должны прочитать о:
- компаньон объекты - похоже на объект класса в Java, но одноэлементный на класс, который не является статическим
@JvmStatic
- аннотация, которая превращает метод сопутствующего объекта в статический метод на внешнем классе для Java interoplateinit
- позволяет avar
свойства инициализируются позже, когда у вас есть четко определенный жизненный циклDelegates.notNull()
- может использоваться вместоlateinit
свойства, которое должен быть установлен по крайней мере один раз перед чтением.вот более полные примеры тестовых классов для Kotlin, которые управляют встроенными ресурсами.
первый копируется и изменяется из Solr-подводные испытания, и перед запуском тестовых случаев настраивает и запускает сервер Solr-Undertow. После выполнения тестов, он очищает все временные файлы, созданные с помощью тестов. Это также гарантирует, что переменные среды и свойства системы являются исправьте перед запуском тестов. Между тестовыми случаями он выгружает все временные загруженные ядра Solr. Тест:
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 }
и еще один запуск AWS DynamoDB local в качестве встроенной базы данных (скопирован и слегка изменен из запуск AWS DynamoDB-local embedded). Этот тест должен взломать
java.library.path
прежде чем что-либо еще произойдет или локальный DynamoDB (используя sqlite с двоичными библиотеками) не будет работать. Затем он запускает сервер для совместного использования для всех тестовых классов и очищает временные данные между тестами. Тест: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 }
Примечание: некоторые части примеров сокращены с
...