๐Ÿ”ง m1์—์„œ mysql testcontainers ์‚ฌ์šฉํ•˜๊ธฐ

SungEun Parkยท2021๋…„ 8์›” 4์ผ
3

๐Ÿ’ก backend-tips

๋ชฉ๋ก ๋ณด๊ธฐ
1/1
post-thumbnail

๐Ÿ’ป m1 + mysql8 + testcontainers

backend ๊ฐœ๋ฐœ์„ ํ•˜๋ฉฐ test ํ™˜๊ฒฝ์„ ์„ค์ •ํ•˜๋Š” ๊ฒƒ์€ ํ•„์ˆ˜์ ์ธ ๊ณผ์ •์ด๋‹ค.

docker๊ฐ€ ๋‚˜์˜ค๊ณ  ๋‚˜์„œ๋ถ€ํ„ฐ๋Š” ํŽธํ•˜๊ฒŒ ์„ค์ •ํ•˜๊ธฐ ์œ„ํ•ด docker-compose๋ฅผ ํ™œ์šฉํ•˜๊ฑฐ๋‚˜ testcontainers๋ฅผ ์ฃผ๋กœ ํ™œ์šฉํ•˜๋Š”๋ฐ,
๊ฐœ์ธ ๋…ธํŠธ๋ถ์„ m1์œผ๋กœ ๋ฐ”๊พธ๊ณ  ๋‚˜์„œ ์ฒ˜์Œ์œผ๋กœ testcontainers๋ฅผ mysql๋กœ ์„ค์ •ํ•˜๋ฉฐ ์ƒ๊ธด ์ด์Šˆ๋ฅผ ์ •๋ฆฌํ•ด๋ณด์•˜๋‹ค.

๐Ÿ‘จโ€๐Ÿ’ป ๊ฒฐ๊ณผ

์‹œ๊ฐ„์ด ์—†์œผ์‹  ๋ถ„๋“ค์„ ์œ„ํ•ด ๋ฐ”๋กœ ์„ฑ๊ณตํ•œ ์ฝ”๋“œ๋ถ€ํ„ฐ..

๋ณดํ†ต์€ testContainers๋ฅผ ์‚ฌ์šฉํ• ๋•Œ์— junit container ๋ฐฉ์‹์œผ๋กœ ๊ตฌํ˜„ํ•˜๋Š”๋ฐ
๋‚˜๋Š” ๊ฐœ์ธ์ ์œผ๋กœ test์— abstract ํด๋ž˜์Šค๋‚˜ ๋„ˆ๋ฌด ๋งŽ์€ ์–ด๋…ธํ…Œ์ด์…˜์„ ๋„ฃ๋Š” ๊ฒƒ์„ ์„ ํ˜ธํ•˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์— ๋ณ„๋„๋กœ lifecycle์„ ๊ด€๋ฆฌํ•˜๋Š” ๋ฐฉ์‹์œผ๋กœ ๊ตฌํ˜„ํ•˜์˜€๋‹ค.

import org.springframework.core.Ordered
import org.springframework.core.annotation.Order
import org.springframework.stereotype.Component
import org.testcontainers.containers.MySQLContainer
import org.testcontainers.utility.DockerImageName
import javax.annotation.PreDestroy

@Component
@Order(value = Ordered.HIGHEST_PRECEDENCE)
class MySqlTestContainer {

    @PreDestroy
    fun stop() {
        MY_SQL_CONTAINER.stop()
    }

    companion object {
        @JvmStatic
        val MY_SQL_CONTAINER: MySQLContainer<*> =
                // image for linux/arm64/v8 m1 support
                DockerImageName.parse("mysql/mysql-server:8.0.26")
                        .asCompatibleSubstituteFor("mysql")
                        .let { compatibleImageName -> MySQLContainer<Nothing>(compatibleImageName) }
                        .apply {
                            withDatabaseName(DATABASE_NAME)
                            withUsername(USERNAME)
                            withPassword(PASSWORD)
                            withEnv("MYSQL_USER", USERNAME)
                            withEnv("MYSQL_PASSWORD", PASSWORD)
                            withEnv("MYSQL_ROOT_PASSWORD", PASSWORD)
                            start()
                        }

        const val DATABASE_NAME: String = "batch_test"
        const val USERNAME: String = "root"
        const val PASSWORD: String = "password"
    }
}

์•„๋ž˜๋Š” ์œ„์˜ ์ปจํ…Œ์ด๋„ˆ๋ฅผ dataSource๋กœ ์—ฐ๊ฒฐํ•˜๋Š” ๋ถ€๋ถ„์ด๋‹ค.

import org.springframework.boot.jdbc.DataSourceBuilder
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.context.annotation.DependsOn
import org.testcontainers.containers.MySQLContainer
import javax.sql.DataSource

@Configuration
class TestDataSourceConfiguration {

    @Bean
    @DependsOn("mySqlTestContainer")
    fun dataSource(): DataSource =
            DataSourceBuilder.create()
                    .url("jdbc:mysql://localhost:" +
                            "${MySqlTestContainer.MY_SQL_CONTAINER.getMappedPort(MySQLContainer.MYSQL_PORT)}/" +
                            MySqlTestContainer.DATABASE_NAME)
                    .driverClassName("com.mysql.cj.jdbc.Driver")
                    .username(MySqlTestContainer.USERNAME)
                    .password(MySqlTestContainer.PASSWORD)
                    .build()

}

๐Ÿšจ ๋งž๋‹ฅ๋œจ๋ฆฐ ์—๋Ÿฌ๋“ค

๐Ÿ”ง mysql docker hub m1 ๋ฏธ์ง€์› ์ด์Šˆ

no matching manifest for linux/arm64/v8 in the manifest list entries

๋จผ์ € MySQLContainer<Nothing>("mysql:8.0.26") ๋ฒ„์ „์œผ๋กœ ์‹œ๋„ํ•˜๋‹ˆ ์œ„์™€ ๊ฐ™์€ ์—๋Ÿฌ๋ฅผ ๋งž๋‹ฅ๋œจ๋ ธ๋‹ค.
์ฐพ์•„๋ณด๋‹ˆ mysql dockerhub์— linux/amd64๋ฒ„์ „๋งŒ ์˜ฌ๋ผ์™€์žˆ๊ณ  m1์— ํ•ด๋‹นํ•˜๋Š” linux/arm64/v8์œผ๋กœ ๋นŒ๋“œ๋œ ๋ฒ„์ „์ด ์—†์—ˆ๋‹ค.
๋”ฐ๋ผ์„œ ์ฐพ์•„๋ณด๋‹ˆ mysql-server dockerhub์—๋Š” m1์— ํ•ด๋‹นํ•˜๋Š” ๋ฒ„์ „๋„ ์˜ฌ๋ผ์™€์žˆ์–ด์„œ ํ•ด๋‹น ๋ฒ„์ „์œผ๋กœ ๋ณ€๊ฒฝํ•˜์˜€๋‹ค.

๐Ÿ”ง testcontainers ์ด๋ฏธ์ง€ ํ˜ธํ™˜ ์ด์Šˆ

java.lang.IllegalStateException: Failed to verify that image 'mysql/mysql-server:8.0.26' is a compatible substitute for 'mysql'. This generally means that you are trying to use an image that Testcontainers has not been designed to use. If this is deliberate, and if you are confident that the image is compatible, you should declare compatibility in code using the `asCompatibleSubstituteFor` method. For example:
   DockerImageName myImage = DockerImageName.parse("mysql/mysql-server:8.0.26").asCompatibleSubstituteFor("mysql");

์•„๋ฌด๋ž˜๋„ docker image๋ฅผ mysql์—์„œ mysql-server๋กœ ๋ฐ”๊พธ๋‹ˆ ์ •๋ง testcontainers ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๊ฐ€ ์‚ฌ์šฉํ•˜๋Š” mysql์ด ๋งž๋Š”์ง€๋ฅผ ๋ฌผ์–ด๋ณด๋Š” ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ–ˆ๋‹ค.
๋”ฐ๋ผ์„œ ์—๋Ÿฌ์—์„œ ์„ค๋ช…ํ•˜๋Š” ๋Œ€๋กœ ์„ค์ •์„ ์ˆ˜์ •ํ•˜์˜€๋‹ค.

DockerImageName.parse("mysql/mysql-server:8.0.26")
                        .asCompatibleSubstituteFor("mysql")
                        .let { compatibleImageName -> MySQLContainer<Nothing>(compatibleImageName) }

๐Ÿ”ง mysql8 docker ์„ค์ • ์ด์Šˆ

java.sql.SQLException: null,  message from server: "Host '172.17.0.1' is not allowed to connect to this MySQL server"

๊ธฐ์กด์—๋Š” ์ฃผ๋กœ postgresql์„ ํ™œ์šฉํ•ด์„œ ์ธ์ง€ mysql8์—์„œ ํ•„์š”ํ•œ ์˜ต์…˜๋“ค์„ ์กฐ๊ธˆ ๋†“์ณค๋˜ ๊ฒƒ ๊ฐ™๋‹ค.
mysql 8 docker ์„ค์ •๋“ค์„ ๊ตฌ๊ธ€๋งํ•ด์„œ env๋ฅผ ์ถ”๊ฐ€ํ•ด์„œ ์ตœ์ข…์ ์œผ๋กœ ์—ฐ๋™์— ์„ฑ๊ณตํ•˜์˜€๋‹ค.

    companion object {
        @JvmStatic
        val MY_SQL_CONTAINER: MySQLContainer<*> =
                // image for linux/arm64/v8 m1 support
                DockerImageName.parse("mysql/mysql-server:8.0.26")
                        .asCompatibleSubstituteFor("mysql")
                        .let { compatibleImageName -> MySQLContainer<Nothing>(compatibleImageName) }
                        .apply {
                            withDatabaseName(DATABASE_NAME)
                            withUsername(USERNAME)
                            withPassword(PASSWORD)
                            withEnv("MYSQL_USER", USERNAME)
                            withEnv("MYSQL_PASSWORD", PASSWORD)
                            withEnv("MYSQL_ROOT_PASSWORD", PASSWORD)
                            start()
                        }

        const val DATABASE_NAME: String = "batch_test"
        const val USERNAME: String = "root"
        const val PASSWORD: String = "password"
    }

๐Ÿ˜„ ๊ฒฐ๋ก 

m1์„ ์“ฐ๋‹ค๋ณด๋‹ˆ ์‹คํ–‰์†๋„๋‚˜ ๋ฐฐํ„ฐ๋ฆฌ ์‹œ๊ฐ„ ๋“ฑ ์–ป์€ ์ด๋“์ด ํฌ์ง€๋งŒ ๋ฐ˜๋Œ€๋กœ ์•„์ง ์ƒํƒœ๊ณ„๊ฐ€ ์กฐ๊ธˆ ๋ถ€์กฑํ•œ ๋ถ€๋ถ„๋“ค์ด ๋‹จ์ ์ธ ๊ฒƒ ๊ฐ™๋‹ค. ํ•˜์ง€๋งŒ ๋Š˜ ๊ทธ๋ ‡๋“ฏ์ด ํ•ด๊ฒฐ๋ฐฉ์•ˆ์€ ์žˆ๊ธฐ ๋งˆ๋ จ์ด๋‹ค. ๋!

profile
backend developer

2๊ฐœ์˜ ๋Œ“๊ธ€

comment-user-thumbnail
2021๋…„ 8์›” 4์ผ

์ •๋ง ๋ฉ‹์ ธ์š”!

๋‹ต๊ธ€ ๋‹ฌ๊ธฐ
comment-user-thumbnail
2021๋…„ 8์›” 8์ผ

์ „์™„๊ทผ์ด ๋ฉ‹์ง€์‹œ๋„ค์š”

๋‹ต๊ธ€ ๋‹ฌ๊ธฐ