概要
.Net FrameworkでTestContainersを導入したので防備録を残しておきます!
TestContainersとは?
Testcontainersは、Dockerコンテナで実行できるデータベースなどのソフトウェアに関して軽量で使い捨て可能なインスタンスを提供するためのオープンソースフレームワークです。
このフレームワークを使うと、運用環境と同等のデータベースインスタンスをテスト環境で何度でも簡単に再現し利用できます。
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のバージョンを変更します。
(ここが一番手間取りました。。。。。。。。。。。)
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/
にならってクラスを作成する
Customer.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
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
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
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コンテナが立ち上がっていることを確認できます
エラーの場合
Assertionの取得件数を変更すると テスト結果はNGになります。
課題
・SQLServerで試せるか
・Dapperでもテストできるか
・github Action CIする
参考
.NET用 Testcontainersを使い始める
TestContainers .Net Framework4.x の設定方法
githubコミット分