テストコンテナとクリーンアーキテクチャ



概要
「外部世界」(DBMS、外部API、ウェブ)と通信する責任がある「ソフトウェアの一部」のような我々のシステムの境界を特定するために、ドメイン許可からの基盤を切り離すこと.TestContainer Technologyを解析し、システム境界のテストスイートを構築します.
からtest containers docsは次のようなテストを簡単に行うことを知っています.
  • データアクセス層の統合テスト:MySQL、PostgreSQLまたはOracleデータベースのコンテナ化されたインスタンスを使用して完全な互換性のためにデータアクセス層のコードをテストするが、開発者のマシン上で複雑なセットアップを必要とせず、あなたのテストは、常に既知のDBの状態で起動します知識の安全性を必要とせず.コンテナ化可能な他のデータベース型も使用できます.
  • アプリケーションの統合テスト:データベース、メッセージキューやWebサーバーなどの依存関係を持つ短寿命テストモードでアプリケーションを実行するため.
  • UI/受け入れテスト:自動UIテストを実施するために、セレンと互換性のある、コンテナ化されたWebブラウザを使用してください.各テストはブラウザの状態、プラグインのバリエーションや自動ブラウザのアップグレードを心配するブラウザの新鮮なインスタンスを取得することができます.そして、各テストセッション、またはテストが失敗した各セッションのビデオ録画を取得します.
  • まあ!我々はペットコードでそれを試みるつもりです.テストコンテナフレームワークを使用しないでください.始めましょう!


    この関数のためにDBアクセス層を書きたいと思います.
    loadEmployees: () -> Either<Error, Employees>
    
    私もDockerインフラストラクチャを使用してテストします.
    私はfileload社員関数のいくつかの統合テストがあることを覚えています.
    @Test
    internal fun loadSomeEmployees()
    
    @Test
    internal fun employeeNotValidEmail()
    
    それらを記述するのを避けることができるように、彼らは全く明確です.
    DBの実装に使用したいのですが、テストコードを複製したくありません.私は両方の実装(ファイルとDB)に使用できるこれらのテストの抽象化を作成するつもりです.
    abstract class LoadEmployeeTest {
    
        @Test
        internal fun loadEmployee() {
            val loadEmployees = instance()
    
            assertThat(loadEmployees()).isEqualTo(
                Either.Right(
                    Employees(
                        listOf(
                            Employee(
                                "Marco", "Sabatini", DateOfBirth(5, 3, 1983),
                                EmailAddress("[email protected]")
                            )
                        )
                    )
                )
            )
        }
    
        @Test
        internal fun employeeNotValid() {
            val loadEmployees = wrongInstance()
    
            assertThat(loadEmployees()).isEqualTo(Either.Left(Error("Error For input string: \"wrong\"")))
        }
    
        abstract fun instance(): () -> Either<Error, Employees>
        abstract fun wrongInstance(): () -> Either<Error, Employees>
    }
    
    基本的に2つの抽象メソッドを実装します.これらのメソッドは、ハッピーパスとコーナーケースの相対関数の実装(ファイルまたはDB)を返さなければなりません.
    ファイルアクセス層には(タナaa !):
    override fun instance(): () -> Either<Error, Employees> =
            loadEmployeeFrom("./target/test-classes/employees.txt")
    
        override fun wrongInstance(): () -> Either<Error, Employees> =
            loadEmployeeFrom("./target/test-classes/employeesNotValid.txt")
    
        @Test
        internal fun fileNotFound() {
    
            val loadEmployeeFromFile = loadEmployeeFrom("NOT_EXIXSTING_FILE")
    
            Assertions.assertThat(loadEmployeeFromFile())
                .isEqualTo(Either.Left(Error("File NOT_EXIXSTING_FILE doesn't exist")))
        }
    
    DB Access Layerのために、(タナaa !):
      @AfterEach
        fun cleanupTest() {
            execute("DELETE from employees")
        }
    
        override fun instance(): () -> Either<Error, Employees> {
            execute("INSERT INTO employees VALUES ('Marco', 'Sabatini','05/03/1983','[email protected]')")
    
            return loadEmployeeWith()
        }
    
        override fun wrongInstance(): () -> Either<Error, Employees> {
            execute("INSERT INTO employees VALUES ('', '','wrong','')")
    
            return loadEmployeeWith()
        }
    
        private fun execute(sql: String) {
            val stmt = connection().createStatement()
            stmt!!.executeUpdate(sql)
            stmt.close()
        }
    
    この手法を呼ぶcontract test また、CodeBaseの特定の動作を定義しなければならない場合には非常に便利です.
    この場合、私は自分のドメインコードと私のインフラコードの間でロードワーカーの振る舞いを定義しました.これは非常に我々は外部のポートのメモリの実装にしたいと我々の受け入れテスト(超高速!

    DockerとDockerの作成
    今ではインフラの部分に移動する時間です.私は、DBMS MySQLインスタンスとJVM Mavenのランタイムが必要です.これらの容器は互いに通信しなければならない.
    これは私が使用したDocker構成ファイルの設定です.
    services:
      mysql:
        image: mysql
        container_name: mysql_server
        ports:
          - "3306:3306"
        environment:
          - MYSQL_ROOT_PASSWORD=pwd
        networks:
          - db-network
    
       db_client:
        build:
          context: ./containers/client
        networks:
          - db-network
        environment:
          - WAIT_HOSTS=mysql:3306
          - WAIT_HOSTS_TIMEOUT=300
          - WAIT_SLEEP_INTERVAL=30
          - WAIT_HOST_CONNECT_TIMEOUT=30
    
      maven:
        image: maven
        container_name: builder
        volumes:
          - ${PWD}:/tmp
        networks:
          - db-network
    
    networks:
      db-network:
    

  • MySQL : DBMS

  • DBRANクライアント:MySQLクライアントのコマンドラインインターフェイスです.スキーマを作成し、他の目的のために使用することができます.
    SchemyPoint Dockerを使用してスキーマや読み込みデータを作成しますplugin MySQLインスタンスを待つのを助けます.
  • case "$@" in
      schema)
        /wait && mysql --host=mysql -uroot -ppwd < employees.sql
        echo "EMPLOYEES schema created!"
      ;;
      demo)
        /wait && mysql --host=mysql -uroot -ppwd < demo.sql
        echo "Could load demo data!"
      ;;
      *)
        exec "$@"
      ;;
    esac
    
    Here Dockerコンテナの設定の詳細を見ることができます.

  • Maven :私のKotlinテストコードが実行されるコンテナです.
  • すべてのCIパイプラインは、このbashスクリプトからオーケストレーションされます.
    #!/bin/bash
    docker-compose up -d mysql
    docker-compose build db_client
    docker-compose run db_client schema
    docker-compose run maven mvn --quiet -f /tmp clean install
    docker-compose run maven mvn -f /tmp surefire-report:report -DshowSuccess=false
    docker-compose down --remove-orphans
    
    コンピュータ上で直接実行するプロジェクトをビルドするか、CIパイプラインを作成するために使用できます.

    CIパイプラインの設定
    私はGHのアクションを使用してgithubに私のCIを構築するためにすべての作品を持っている.
    下.Github/フォルダにこの設定があります.
    name: docker-compose-actions-workflow
    on: push
    jobs:
      test:
        runs-on: ubuntu-latest
        steps:
          - uses: actions/checkout@v2
          - name: CI
            run: script/ci
          - name: DEMO
            run: script/demo
    
    これは、押すと、CIとデモスクリプトを実行するビルドを開始します.
    tab actions すべてのビルドを出力で見ることができます.
    [INFO] 
    [INFO] -------------------------------------------------------
    [INFO]  T E S T S
    [INFO] -------------------------------------------------------
    [INFO] Running com.kata.testcontainers.infrastructure.DBLoadEmployeeTest
    [INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.561 s - in com.kata.testcontainers.infrastructure.DBLoadEmployeeTest
    [INFO] Running com.kata.testcontainers.infrastructure.FileLoadEmployeeTest
    [INFO] Tests run: 3, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.002 s - in com.kata.testcontainers.infrastructure.FileLoadEmployeeTest
    [INFO] 
    [INFO] Results:
    [INFO] 
    [INFO] Tests run: 5, Failures: 0, Errors: 0, Skipped: 0
    containers ---
    
    デモ出力:
    ######## Employees ########
    First Name: Marco
    Last Name: Sabatini
    Email: EmailAddress(value=[email protected])
    BirthDay: DateOfBirth(day=5, month=3, year=1983)
    ....
    
    私は念頭に置いて検証、パフォーマンスや品質のゲートステップを追加することができます

    考慮
    プロジェクトCodeBaseの下にインフラストラクチャを持つことは、全体的な生態系を理解し、devと操作の間の距離をカットするのに役立ちます.このような状況では、コンテナのクラウドサービス(AWS上のEX . ECS)にコンテナを配備することができました.また、CIの中で見たり、デモを行ったりするのと同じように動作します.
    もちろん、組織の観点から、これはまた、コードを開発することができて、必要な基盤を選んで、つくることができる人々を持つことを意味します.
    開発者として我々はコードのIDE“ユーザー”またはシステムエンジニアだけについて考えていない!

    参考文献
  • Originally Posted on
  • Exercise Github repository
  • Testcontainers
  • Getting Started with Contract Tests