CI env
CI env
deps
APP
push
config
jar
run
test
+ Это работает
- CI-окружение всегда включено — лишняя трата ресурсов
- Все зависимости также всегда включены
- Невозможно запустить тесты двух веток одновременно
- Очень тяжело настроить локально
- Невозможно тестировать старт/стоп/рестарт
+ Легко запускать локально
CI env
CI env
deps
APP
push
docker
run
test
docker-compose
deps
APP
push
run
test
+/- Проще настроить локально, но всё еще нужно согласовывать несколько мест
+ Не нужно держать отдельное окружение и зависимости
+ Можно тестировать разные ветки одновременно
- Всё еще невозможно тестировать старт/стоп/рестарт
- Сложности с запуском
version: '2.1'
services:
  web:
    build: .
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_started
  redis:
    image: redis
  db:
    image: db
    healthcheck:
      test: "some test here"version: '3'
services:
  web:
    build: .
    depends_on: [ db, redis ]
  redis:
    image: redis
    command: [ "./wait-for-it.sh", ... ]
  db:
    image: redis
    command: [ "./wait-for-db.sh", ... ]
version: '3'
services:
  postgres:
    ...
  my_service:
    depends_on: [ postgres ]
    ...
  sbt:
    depends_on: [ my_service ]
    ...
docker-compose up -d postgresdocker-compose run sbt
down $?
docker-compose up -d my_service
wait_until 10 2 docker-compose exec -T \ 
  my_service sh -c "netstat -ntlp | grep 80 || exit 1"wait_until 10 2 docker-compose exec -T \ 
  postgres psql postgresql://my_user:my_pass@postgres:5432/my_service \ 
  -c "SELECT PostGIS_version();"function down {
    echo "Exiting with code $1"
    if [[ $1 -eq 0 ]]; then
        docker-compose down
        exit $1
    else
        docker-compose logs -t postgres my_service
        docker-compose down
        exit $1
    fi
}val pgContainer: PostgreSQLContainer = 
  PostgreSQLContainer("postgres:9.6")val pgUrl: String = pgContainer.jdbcUrl
val pgPort: Int = pgContainer.mappedPort(5432)pgContainer.finished()pgContainer.starting()class GenericContainer(
  imageName: String,
  exposedPorts: Seq[Int] = Seq(),
  env: Map[String, String] = Map(),
  command: Seq[String] = Seq(),
  classpathResourceMapping: Seq[(String, String, BindMode)] = Seq(),
  waitStrategy: Option[WaitStrategy] = None
) ...class PostgresqlSpec extends FlatSpec 
  with ForAllTestContainer {  override val container = PostgreSQLContainer()  "PostgreSQL container" should "be started" in {
    Class.forName(container.driverClassName)
    val connection = DriverManager
      .getConnection(container.jdbcUrl, container.username, container.password)
    // test some stuff
  }
}val pgContainer = PostgreSQLContainer()
val myContainer = MyContainer()
override val container = 
  MultipleContainers(pgContainer, myContainer)deps
APP
push
run
test
+ Есть гибкие healthcheck'и
+ Наконец, можно тестировать старт/стоп/рестарт
+ Никаких *.sh-файликов в репозитории
+ Можно конфигурировать приложение и тесты как угодно гибко
+ Одинаково легко запускается на CI и локально
+ Не нужно держать отдельное окружение и зависимости
+ Можно тестировать разные ветки одновременно
Exception encountered when invoking run on a nested suite - Mapped port can only be obtained after the container is started
class MySpec extends FlatSpec 
  with ForAllTestContainer {  val pgCont = PostgreSQLContainer()  override val container = MultipleContainers(appCont, pgCont)  val appCont = 
    AppContainer(pgCont.jdbcUrl, pgCont.username, pgCont.password)  // tests here
}class MyTest extends FreeSpec 
  with BeforeAndAfterAll {
  lazy val pgCont = PostgreSQLContainer()
  lazy val appCont = 
    AppContainer(pgCont.jdbcUrl, pgCont.username, pgCont.password)
  override def beforeAll(): Unit = ??? // start all containers
  override def afterAll(): Unit = ??? // shutdown all containers
  // tests here
}org.postgresql.util.PSQLException: Connection to localhost:32787 refused. Check that the hostname and port are correct and that the postmaster is accepting TCP/IP connections.
@Override
public String getJdbcUrl() {
    return "jdbc:postgresql://" + 
        getContainerIpAddress() + 
        ":" + 
        getMappedPort(POSTGRESQL_PORT) + 
        "/" + 
        databaseName;
}Рекомендация разработчиков testcontainers: создавать руками network для взаимодействия между контейнерами
class MyTest extends FreeSpec with BeforeAndAfterAll {
  val network: Network = Network.newNetwork()
  val dbName = "some_db"
  val pgContainerAlias = "postgres"
  val jdbcUrl = s"jdbc:postgresql://$pgContainerAlias:5432/$dbName"
  lazy val pgCont = {
    val c = PostgreSQLContainer("postgres:9.6")
    c.container.withNetwork(network)
    c.container.withNetworkAliases(pgContainerAlias)
    c.container.withDatabaseName(dbName)
    c
  }
  lazy val appCont = {
    val c = AppContainer(jdbcUrl, pgCont.username, pgCont.password)
    c.container.withNetwork(network)
    c
  }
  override def beforeAll(): Unit = ???
  override def afterAll(): Unit = ??? // shutdown containers + network
  // tests here
}class MyTest extends FreeSpec 
  with ForAllTestContainer 
  with BeforeAndAfterAll {
  val network: Network = Network.newNetwork()
  val dbName = "some_db"
  val pgContainerAlias = "postgres"
  val jdbcUrl = s"jdbc:postgresql://$pgContainerAlias:5432/$dbName"
  lazy val pgCont = {
    val c = PostgreSQLContainer("postgres:9.6")
    c.container.withNetwork(network)
    c.container.withNetworkAliases(pgContainerAlias)
    c.container.withDatabaseName(dbName)
    c
  }
  lazy val appCont = {
    val c = AppContainer(jdbcUrl, pgCont.username, pgCont.password)
    c.container.withNetwork(network)
    c
  }
  override val container = MultipleContainers(pgCont, appCont)
  override def afterAll(): Unit = ??? // shutdown network
  // tests here
}val hostIp = ???
AppContainer(sparkJobServerMockHost = hostIp)
val sparkJobServerMock = new SparkJobServerMock()
sparkJobServerMock.init(someData)
val apiResult = appApi.callMethod()
assert(apiResult == someData)
val sparkJobServerMock = new SparkJobServerMock()
val sparkJobServerMock = new SparkJobServerMock()
sparkJobServerMock.init(someData)
val sparkJobServerMock = new SparkJobServerMock()
sparkJobServerMock.init(someData)
val apiResult = appApi.callMethod()
val sparkJobServerMock = new SparkJobServerMock()
sparkJobServerMock.init(someData)
val apiResult = appApi.callMethod()
assert(apiResult == someData)val client: com.github.dockerjava.api.DockerClient =
  DockerClientFactory
    .instance
    .clientval networkInfo: com.github.dockerjava.api.model.Network =
  client
    .inspectNetworkCmd()
    .withNetworkId(network.getId)
    .exec()val hostIp: String = 
  networkInfo
    .getIpam
    .getConfig
    .get(0)
    .getGatewaylmnet89@gmail.com
Бадальянц Юрий, 2018