文章目录
- 项目地址
- 一、Unit Testing
- 1.1 创建X unit 测试项目
-
- 1.2 创建CreateEntryDtoValidator测试
- 1.3 创建CreateEntryDtoValidator测试
- 二、Integration test
- 2.1 创建Integration test环境
-
- 2.2 配置基础设置
- 1. 数据库链接DevHabitWebAppFactory
- 2.创建测试共享类IntegrationTestCollection
- 3. 注入登录用户
- 2.3 添加测试用例
-
项目地址
dbt
airflow
一、Unit Testing

1.1 创建X unit 测试项目
1. 创建项目目录
- 创建测试

- 目录

2. 管理包
- 修改Packages.props

- 修改项目本身的包,并且添加项目引用到api项目
<Project Sdk="Microsoft.NET.Sdk"><PropertyGroup><IsPackable>false</IsPackable></PropertyGroup><ItemGroup><PackageReference Include="coverlet.collector" /><PackageReference Include="Microsoft.NET.Test.Sdk" /><PackageReference Include="xunit" /><PackageReference Include="xunit.runner.visualstudio" /></ItemGroup><ItemGroup><ProjectReference Include="..\DevHabit\DevHabit.Api\DevHabit.Api.csproj" /></ItemGroup><ItemGroup><Using Include="Xunit" /></ItemGroup></Project>
- 在api的项目配置中添加,我们就可以引用internal 类型在Unitest里

1.2 创建CreateEntryDtoValidator测试
- 创建CreateEntryDtoValidatorTests用来测试

namespace DevHabit.UnitTests.Validators;
public class CreateEntryDtoValidatorTests
{private readonly CreateEntryDtoValidator _validator = new();[Fact] public async Task Validate_ShouldSucceed_WhenInputDtoIsValid(){var dto = new CreateEntryDto{HabitId = Habit.NewId(),Value = 1,Date = DateOnly.FromDateTime(DateTime.UtcNow)};ValidationResult validationResult = await _validator.ValidateAsync(dto);Assert.True(validationResult.IsValid);Assert.Empty(validationResult.Errors); }[Fact]public async Task Validate_ShouldFail_WhenHabitIdIsEmpty(){var dto = new CreateEntryDto{HabitId = string.Empty,Value = 1,Date = DateOnly.FromDateTime(DateTime.UtcNow)};ValidationResult validationResult = await _validator.ValidateAsync(dto);Assert.False(validationResult.IsValid); ValidationFailure validationFailure = Assert.Single(validationResult.Errors); Assert.Equal(nameof(CreateEntryDto.HabitId), validationFailure.PropertyName); }
}
1.3 创建CreateEntryDtoValidator测试
public sealed class EncryptionServiceTests
{private readonly EncryptionService _encryptionService;public EncryptionServiceTests(){IOptions<EncryptionOptions> options = Options.Create(new EncryptionOptions{Key = Convert.ToBase64String(RandomNumberGenerator.GetBytes(32))});_encryptionService = new EncryptionService(options);}[Fact]public void EncryptAndDecrypt_ShouldReturnOriginalPlainText(){const string plainText = "sensitive data";string cipherText = _encryptionService.Encrypt(plainText);string decryptedText = _encryptionService.Decrypt(cipherText);Assert.Equal(plainText, decryptedText);}
}
二、Integration test
2.1 创建Integration test环境
1. 安装所需要的包
- 用于测试api

- 用于测试数据库

2.2 配置基础设置

1. 数据库链接DevHabitWebAppFactory
- 测试时,应用只连接到 DevHabitWebAppFactory 启动的那个 Postgres 容器。测试结束后,DisposeAsync() 会自动停止并清理容器
namespace DevHabit.IntegrationTests.Infrastructure;
public sealed class DevHabitWebAppFactory : WebApplicationFactory<Program>, IAsyncLifetime
{private readonly PostgreSqlContainer _postgresContainer = new PostgreSqlBuilder().WithImage("postgres:17.2").WithDatabase("devhabit").WithUsername("postgres").WithPassword("postgres").Build();protected override void ConfigureWebHost(IWebHostBuilder builder){builder.UseSetting("ConnectionStrings:Database", _postgresContainer.GetConnectionString());}public async Task InitializeAsync(){await _postgresContainer.StartAsync();}public new async Task DisposeAsync(){await _postgresContainer.StopAsync();}
}
2.创建测试共享类IntegrationTestCollection
- 让多个测试类能共享同一个 DevHabitWebAppFactory
namespace DevHabit.IntegrationTests.Infrastructure;
[CollectionDefinition(nameof(IntegrationTestCollection))]
public sealed class IntegrationTestCollection : ICollectionFixture<DevHabitWebAppFactory>;
3. 注入登录用户
- 由于测试api需要登录用户,所以创建用户并且保存token
namespace DevHabit.IntegrationTests.Infrastructure;
public abstract class IntegrationTestFixture(DevHabitWebAppFactory factory) : IClassFixture<DevHabitWebAppFactory>
{private HttpClient? _authorizedClient;public HttpClient CreateClient() => factory.CreateClient();public async Task<HttpClient> CreateAuthenticatedClientAsync(string email = "test@test.com",string password = "Test123!"){if (_authorizedClient is not null){return _authorizedClient;}HttpClient client = CreateClient();bool userExists;using (IServiceScope scope = factory.Services.CreateScope()){using ApplicationDbContext dbContext = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();userExists = await dbContext.Users.AnyAsync(u => u.Email == email);}if (!userExists){HttpResponseMessage registerResponse = await client.PostAsJsonAsync(Routes.Auth.Register,new RegisterUserDto{Email = email,Name = email,Password = password,ConfirmPassword = password});registerResponse.EnsureSuccessStatusCode();}HttpResponseMessage loginResponse = await client.PostAsJsonAsync(Routes.Auth.Login,new LoginUserDto{Email = email,Password = password});loginResponse.EnsureSuccessStatusCode();AccessTokensDto? loginResult = await loginResponse.Content.ReadFromJsonAsync<AccessTokensDto>();if (loginResult?.AccessToken is null){throw new InvalidOperationException("Failed to get authentication token");}client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", loginResult.AccessToken);_authorizedClient = client;return client;}
}
namespace DevHabit.IntegrationTests.Infrastructure;
public static class Routes
{public static class Auth{public const string Register = "auth/register";public const string Login = "auth/login";}public static class Habits{public const string Create = "habits";}
}
2.3 添加测试用例
1. 测试用户创建
public sealed class AuthenticationTests(DevHabitWebAppFactory factory) : IntegrationTestFixture(factory)
{[Fact]public async Task Register_ShouldSucceed_WithValidParameters(){var dto = new RegisterUserDto{Name = "register@test.com",Email = "register@test.com",Password = "Test123!",ConfirmPassword = "Test123!"};HttpClient client = CreateClient();HttpResponseMessage response = await client.PostAsJsonAsync(Routes.Auth.Register, dto);Assert.Equal(HttpStatusCode.OK, response.StatusCode);}[Fact]public async Task Register_ShouldReturnAccessTokens_WithValidParameters(){var dto = new RegisterUserDto{Name = "register1@test.com",Email = "register1@test.com",Password = "Test123!",ConfirmPassword = "Test123!"};HttpClient client = CreateClient();HttpResponseMessage response = await client.PostAsJsonAsync(Routes.Auth.Register, dto);response.EnsureSuccessStatusCode();AccessTokensDto? accessTokensDto = await response.Content.ReadFromJsonAsync<AccessTokensDto>();Assert.NotNull(accessTokensDto);}
}
2. 测试添加Habits
public sealed class HabitsTests(DevHabitWebAppFactory factory) : IntegrationTestFixture(factory)
{[Fact]public async Task CreateHabit_ShouldSucceed_WithValidParameters(){var dto = new CreateHabitDto{Name = "Read Books",Description = "Read technical books to improve skills",Type = HabitType.Measurable,Frequency = new FrequencyDto{Type = FrequencyType.Daily,TimesPerPeriod = 1},Target = new TargetDto{Value = 30,Unit = "pages"}};HttpClient client = await CreateAuthenticatedClientAsync();HttpResponseMessage response = await client.PostAsJsonAsync(Routes.Habits.Create, dto);Assert.Equal(HttpStatusCode.Created, response.StatusCode);Assert.NotNull(response.Headers.Location);Assert.NotNull(await response.Content.ReadFromJsonAsync<HabitDto>());}
}