.Net Framework4.8でTestContainersチュートリアルを試す

# 概要
.Net FrameworkでTestContainersを導入したので防備録を残しておきます！


# TestContainersとは？
```
Testcontainersは、Dockerコンテナで実行できるデータベースなどのソフトウェアに関して軽量で使い捨て可能なインスタンスを提供するためのオープンソースフレームワークです。

このフレームワークを使うと、運用環境と同等のデータベースインスタンスをテスト環境で何度でも簡単に再現し利用できます。
```
https://tech.dentsusoken.com/entry/2023/12/08/Testcontainers%E3%82%92%E4%BD%BF%E3%81%A3%E3%81%A6%E3%83%86%E3%82%B9%E3%83%88%E3%82%92%E5%8A%B9%E7%8E%87%E5%8C%96%E3%81%97%E3%82%88%E3%81%86


# TestContainersで実現したいこと
データベースを含めた結合テストを効率化すること。


# 開発環境
名称|バージョン|説明
---|----------|----
Windows|10|OS
VisualStudio|Community 2022| IDE
.Net FrameWork|4.8|WindowsFormのフレームワーク
Docker Desktop for windows||DockerのGUI版

# NuGet関係のパッケージ


名称|バージョン|説明
---|----------|----
TestContainers|3.3.0|データアクセス層のテストコンテナライブラリ
TestContainers.PostgreSql|3.3.0|TestContainersのPostgresqlインスタンス
xUnit|2.9.3|C#のユニットテストのフレームワーク
xunit.runner.msbuild|2.9.3|xUnitの実行環境
xunit.runner.visualstudio|3.1.0|xUnitの実行環境(visual studioのテストエクスプローラー)
Npgsql|8.0.6|Postgresqlにアクセスするためのライブラリ
Docker.DotNet|3.125.15|.NetのDocker用ライブラリ(TestContainerを導入すると自動的に追加される)
Docker.DotNet.X509|3.125.15|.NetのDocker用ライブラリ(TestContainerを導入すると自動的に追加される)


# 手順

## Nugetでパッケージをインストールする。

NuGet関係のパッケージをインストールする

## NugetのDocker.DotNetをアセンブルバインディング
TestContainersが要求するDocker.DotNetのバージョンが3.128.00なのでバージョンの不一致が発生します。
そこでアセンブルバインディグで参照するDocker.DotNetのバージョンを変更します。
（ここが一番手間取りました。。。。。。。。。。。）


```diff:App.config
      <dependentAssembly>
        <assemblyIdentity name="Docker.DotNet" publicKeyToken="e628c633e2cf4146" culture="neutral" />
-        <bindingRedirect oldVersion="3.125.0.0" newVersion="3.125.0.0" />
+        <bindingRedirect oldVersion="0.0.0.0-3.125.0.0" newVersion="3.125.0.0" />
      </dependentAssembly>

      <dependentAssembly>
        <assemblyIdentity name="Docker.DotNet.X509" publicKeyToken="e628c633e2cf4146" culture="neutral" />
-        <bindingRedirect oldVersion="3.125.0.0" newVersion="3.125.0.0" />
+        <bindingRedirect oldVersion="0.0.0.0-3.125.0.0" newVersion="3.125.0.0" />
      </dependentAssembly>


```






## テスト用のクラスを作成する

https://testcontainers.com/guides/getting-started-with-testcontainers-for-dotnet/
にならってクラスを作成する

フォルダ構造
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2710623/ae920741-4b8d-4af3-ab5e-fa39b7e155f4.png)

### Customer.cs

```cs:Customer.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace WindowsForm_sample
{
    // Fix for CS8370: Replace 'record struct' with 'struct' as C# 7.3 does not support 'record struct'.  
    // Fix for CS9113: Ensure the parameters are used in the struct.  

    public struct Customer
    {
        public long Id { get; }
        public string Name { get; }

        public Customer(long id, string name)
        {
            Id = id;
            Name = name;
        }
    }
}

```

### DbConnectionProvider
```cs:DbConnectionProvider.cs
using System;
using System.Collections.Generic;
using System.Data.Common;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Npgsql;

namespace WindowsForm_sample
{
    public sealed class DbConnectionProvider
    {
        private readonly string _connectionString;

        public DbConnectionProvider(string connectionString)
        {
            _connectionString = connectionString;
        }

        public DbConnection GetConnection()
        {
            return new NpgsqlConnection(_connectionString);
        }
    }
}

```

### CustomerService
```cs:CustomerService.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Data.Common;
using Npgsql;


namespace WindowsForm_sample
{
    public class CustomerService
    {
        private readonly DbConnectionProvider _dbConnectionProvider;

        public CustomerService(DbConnectionProvider dbConnectionProvider)
        {
            _dbConnectionProvider = dbConnectionProvider;
            CreateCustomersTable();
        }

        public IEnumerable<Customer> GetCustomers()
        {
            IList<Customer> customers = new List<Customer>();

            using (var connection = _dbConnectionProvider.GetConnection())
            {
                using (var command = connection.CreateCommand())
                {
                    command.CommandText = "SELECT id, name FROM customers";
                    connection.Open();

                    using (var dataReader = command.ExecuteReader())
                    {
                        while (dataReader.Read())
                        {
                            var id = dataReader.GetInt64(0);
                            var name = dataReader.GetString(1);
                            customers.Add(new Customer(id, name));
                        }
                    }
                }
            }

            return customers;
        }

        public void Create(Customer customer)
        {
            using (var connection = _dbConnectionProvider.GetConnection())
            {
                using (var command = connection.CreateCommand())
                {
                    var id = command.CreateParameter();
                    id.ParameterName = "@id";
                    id.Value = customer.Id;

                    var name = command.CreateParameter();
                    name.ParameterName = "@name";
                    name.Value = customer.Name;

                    command.CommandText = "INSERT INTO customers (id, name) VALUES(@id, @name)";
                    command.Parameters.Add(id);
                    command.Parameters.Add(name);
                    connection.Open();
                    command.ExecuteNonQuery();
                }
            }
        }

        private void CreateCustomersTable()
        {
            using (var connection = _dbConnectionProvider.GetConnection())
            {
                using (var command = connection.CreateCommand())
                {
                    command.CommandText = "CREATE TABLE IF NOT EXISTS customers (id BIGINT NOT NULL, name VARCHAR NOT NULL, PRIMARY KEY (id))";
                    connection.Open();
                    command.ExecuteNonQuery();
                }
            }
        }
    }
}


```



### CustomerServiceTest
```cs:CustomerServiceTest.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Xunit;
using Testcontainers.PostgreSql;

namespace WindowsForm_sample
{
    public class CustomerServiceTest : IAsyncLifetime
    {
        public readonly PostgreSqlContainer _postgres = new PostgreSqlBuilder()
            .WithImage("postgres:15-alpine").Build();


        public Task InitializeAsync()
        {
            return _postgres.StartAsync();
        }

        public Task DisposeAsync()
        {
            return _postgres.DisposeAsync().AsTask();
        }

        [Fact]
        public void ShouldReturnTwoCustomers()
        {
            // Given  

            var customerService = new CustomerService(new DbConnectionProvider(_postgres.GetConnectionString()));

            // When  
            customerService.Create(new Customer(1, "George"));
            customerService.Create(new Customer(2, "John"));
            var customers = customerService.GetCustomers();

            // Then  
            Assert.Equal(2, customers.Count());
        }
    }
}


```





## 




# 結果

## 実行中の様子
テスト中の画面
dockerコンテナが立ち上がっていることを確認できます
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2710623/92f9ed3f-608b-4973-bcbf-11a34f04fbf2.png)

テスト完了後の画面
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2710623/d4b05a80-bfe6-432c-bd6e-e8e328cd4a83.png)



## エラーの場合
Assertionの取得件数を変更すると　テスト結果はNGになります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2710623/18d5c05c-57a5-4ad8-9e8e-9cbc99704891.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2710623/2d394bc6-127b-4bb2-ab32-5d3f2040c244.png)



# 課題
・SQLServerで試せるか
・Dapperでもテストできるか
・github Action　CIする

# 参考

## .NET用　Testcontainersを使い始める

https://testcontainers.com/guides/getting-started-with-testcontainers-for-dotnet/

## TestContainers .Net Framework4.x　の設定方法

https://dotnet.testcontainers.org/full_framework/


## githubコミット分

https://github.com/RYA234/WindowsForm_sample/commit/e3d6ab396f25d746a601b58fac3664dac42d1cbc