XUnit数据共享与并行测试

这篇具有很好参考价值的文章主要介绍了XUnit数据共享与并行测试。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

引言

在单元或者集成测试的过程中,需要测试的用例非常多,如果测试是一条一条过,那么需要花费不少的时间。从 V2 开始,默认情况下 XUnit 自动配置并行(参考资料),大大提升了测试速度。本文将对 ASP.NET CORE WEBAPI 程序进行集成测试,并探讨 XUnit 的数据共享与测试并行的方法。

XUnit默认在一个类内的测试代码是串行执行的,而在不同类的测试代码是并行执行的。

集成测试

对于集成测试来说,我们有一些比较重的资源初始化,而我并不想他们在并行执行中重复初始化,因此需要将并行执行的资源共享。

我们现在的测试类是这样的:

    public class ProgramTests : IClassFixture<WebApplicationFactory<Program>>
    {
        private readonly WebApplicationFactory<Program> _factory;
        private readonly ITestOutputHelper testOutputHelper;
        private readonly HttpClient _client;
        public ProgramTests(WebApplicationFactory<Program> factory, ITestOutputHelper testOutputHelper)
        {
            _factory = factory;
            this.testOutputHelper = testOutputHelper;
            _client = _factory.WithWebHostBuilder(builder =>
            {
                builder.UseEnvironment(Environments.Production);
            }).CreateClient(new WebApplicationFactoryClientOptions() { BaseAddress = new Uri("http://localhost:9000") });
            var token = TokenHelper.GetToken("username", "password");
            _client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);
            // Act
        }

        [Fact]
        public async Task V1Legacy_GetDeviceInfoes()
        {
            string url = "url1";
            // Arrange
            testOutputHelper.WriteLine($"Testing:{url}");
            var response = await _client.GetAsync(url);

            // Assert
            response.EnsureSuccessStatusCode(); // Status Code 200-299
            var result = await response.Content.ReadAsStringAsync();
            var target = JsonSerializer.Deserialize<DeviceInfo>(result, new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
            Assert.NotNull(target); 
        }

        [Fact]
        public async Task V1Legacy_GetCurrent()
        {
            var url = "url2";
            // Arrange
            testOutputHelper.WriteLine($"Testing:{url}");
            var response = await _client.GetAsync(url);

            // Assert
            response.EnsureSuccessStatusCode(); // Status Code 200-299
            var result = await response.Content.ReadAsStringAsync();
            var target = JsonSerializer.Deserialize<DeviceDataDto>(result, new JsonSerializerOptions {PropertyNameCaseInsensitive = true });
            Assert.NotNull(target);
        }

        [Theory]
        [InlineData("url3")]
        [InlineData("url4")]
        public async Task V1Legacy_CheckUrlExist(string url)
        {
            // Arrange
            testOutputHelper.WriteLine($"Testing:{url}");
            var request = new HttpRequestMessage(HttpMethod.Head, url);
            var response = await _client.SendAsync(request);

            // Assert
            Assert.NotEqual(404, (int)response.StatusCode); 
        }
    }

在这个测试中,使用 IClassFixture 进行集成测试,确保同一个类之内的代码共享同一个资源,不同测试方法串行执行。

TIPS: 这里我使用 HEAD 请求来探查给定地址是否存在,ASP. NET CORE 会默认拒绝这个请求(返回406),但是不会提示 404 的错误。

现在的运行时间是这样的:
XUnit数据共享与并行测试

单类优化

首先研究为什么这个程序花费了如此多的时间执行测试,XUnit 在进行不同 Fact 的测试时,会生成不同的对象,我们已经通过实现 IClassFixture<WebApplicationFactory<Program>> 共享了必要的数据吗?

并没有,XUnit 只是注入了 WebApplicationFactory<Program>,而我们在构造函数中执行了很多费时间的操作,包括构造 HttpClient ,获取 token 等。由于获取 token 的函数需要调用外部服务花费了很长的时间,我们可以尝试注入 HttpClient 进行优化。

请注意:大多数情况注入 HttpClient 不是一个好主意,更推荐利用 WebApplicationFactory<Program> 对每个测试动态生成 HttpClient 以保证 HttpClient 是初始干净的状态。

我们加入一个新的类:

    public class SharedHttpClientFixture : IDisposable
    {
        public HttpClient Client { get; init; }

        public SharedHttpClientFixture()
        {
            WebApplicationFactory<YourAssemblyName.Program> factory = new();
            
                Client = factory.WithWebHostBuilder(builder =>
                        {
                            builder.UseEnvironment(Environments.Production);
                        }).CreateClient(new WebApplicationFactoryClientOptions() { BaseAddress = new Uri("http://localhost:9000") });

            var token = TokenHelper.GetToken("username", "password");
            Client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", token);
            // Act
        }

        public void Dispose()
        {
            //throw new NotImplementedException();
        }
    }

并修改测试类的签名:

    public class ProgramTests : IClassFixture<SharedHttpClientFixture>
    {
        private readonly ITestOutputHelper testOutputHelper;
        private readonly HttpClient _client;
        public ProgramTests(SharedHttpClientFixture httpClientFixture, ITestOutputHelper testOutputHelper)
        {
            _client = httpClientFixture.Client;
            this.testOutputHelper = testOutputHelper;
        }

改完之后,速度提升效果还是非常显著的:
XUnit数据共享与并行测试

跨类串行

我们多数情况下不会将所有的测试都放在一个类中,对于多个类,我们需要跨类共享。XUnit 使用 ICollectionFixture<> 支持跨类共享。代码主体拆成两个类,并修改类签名如下:

    [Collection("V1 Test Fixture")]
    public class ProgramTests
    {
    ...
    }
    [Collection("V1 Test Fixture")]
    public class UploadTests
    {
	....
	}

我们需要新定义一个类,这个类没有实质性作用,只是作为标识:

    [CollectionDefinition("V1 Test Fixture")]
    public class TestCollection : ICollectionFixture<SharedHttpClientFixture>
    {
        // This class has no code, and is never created. Its purpose is simply
        // to be the place to apply [CollectionDefinition] and all the
        // ICollectionFixture<> interfaces.
    }

我们针对多个类进行测试:
XUnit数据共享与并行测试

跨类并行(数据不共享)

我们注意到,不同类使用了相同的 Collection 进行标注,因此他们实际上会进行同步调度——上一个执行完成后才会开始执行下一个测试。我们如果使用并行会怎么样呢?显然,修改 Colleciton 会对每个类都生成一次需要注入对象,数据不能直接被共享。

    [Collection("V1 Test Fixture1")]
    public class ProgramTests
    {
    ...
    }
    [Collection("V1 Test Fixture")]
    public class UploadTests
    {
	....
	}
	[CollectionDefinition("V1 Test Fixture1")]
    public class Test1Collection : ICollectionFixture<SharedHttpClientFixture>
    {
        // This class has no code, and is never created. Its purpose is simply
        // to be the place to apply [CollectionDefinition] and all the
        // ICollectionFixture<> interfaces.
    }
        [CollectionDefinition("V1 Test Fixture")]
    public class TestCollection : ICollectionFixture<SharedHttpClientFixture>
    {
        // This class has no code, and is never created. Its purpose is simply
        // to be the place to apply [CollectionDefinition] and all the
        // ICollectionFixture<> interfaces.
    }

初始化语句会被执行两次,我们实现了并行,但是数据并不是共享的。(大多数情况下已经够用了。)
XUnit数据共享与并行测试

跨类并行(数据共享)

由于任务并行无法得知其他任务的工作状态,这个时候数据共享可能会引入很多线程问题(竞争、死锁等),因此不太建议在这种情况下进行共享,我最终也是使用的并行不共享的方式实现。如果我们非得这么用,也不是不行,我们需要小心处理线程同步问题,以互斥锁为例:

 public class SharedHttpClientFixture : IDisposable
    {
        private static HttpClient _httpClient;
        public HttpClient Client => GetClient();

        private HttpClient GetClient()
        {
            if (_httpClient == null) Init();
            return _httpClient;
        }


        public static Mutex count = new();

        public SharedHttpClientFixture()
        {

        }

        private void Init()
        {
            count.WaitOne();
            if(_httpClient == null)
            {
				...
            }
            count.ReleaseMutex();
        }

        public void Dispose()
        {
            //throw new NotImplementedException();
        }
}

这样多个类型使用静态变量实现了共享,并利用互斥锁保证初始化只执行一次。
XUnit数据共享与并行测试

由于引入了线程同步机制,这种情况下,并行测试并不一定意味着性能会更好,实际上往往还会更差。

生命周期

XUnit 对共享的数据类型执行以下策略:

  • IClassFixture,类的第一个测试方法执行之前,会对注入对象进行初始化。随后每一个方法都会生成测试的类的新对象,并将注入对象传递给他们,在测试类中所有测试方法执行完毕后销毁。
  • ICollectionFixture,多个类中执行的第一个测试方法之前会对注入对象进行初始化。随后每一个方法都会生成测试类的新对象,并且将注入对象传递给他们,在所有测试类的最后一个方法执行完毕之后销毁。

Program 可见性

默认情况下 Program 是其他项目不可见的,这样会导致 WebApplicationFactory<Program> 提示错误。.NET 6 开始引入了 minimal API,我的项目是升级而来,并没有使用到这个东西,所以 Program 类是对外可见的。

    public class Program
    {
        static void Main(string[] args)
        {
            var builder = WebApplication.CreateBuilder(args);
            builder.WebHost.UseUrls("http://*:9000");
            ConfigureServices(builder.Services, builder.Configuration);
            var app = builder.Build();
            Configure(app);
            app.Run();
        }
    }

如果使用 Minimal API,那么你需要在项目文件中对测试项目公开可见性。

<ItemGroup>
     <InternalsVisibleTo Include="MyTestProject" />
</ItemGroup>

或者在 Program.cs 的最后加上一行。

var builder = WebApplication.CreateBuilder(args);
// ... Configure services, routes, etc.
app.Run();
+ public partial class Program { }

注意事项

Microsoft.VisualStudio.TestPlatform.TestHost 命名空间也有一个 Program 类,如果你自己实现自定义类型,由于默认引用,不注意就使用了这个东西,而不是你 API 的 Program 类,这样会导致测试无法运行,提示:“找不到 testHost.dep.json”这样的错误,所以尽量使用带命名空间的限定名称。

结论

在 XUnit 测试中,可以使用 IClassFixtureICollectionFixture 来进行数据共享,对于相同类之间的测试会默认进行的串行测试,对不同类之间共享数据的情况,也会进行串行调用。对于在不同类的测试,推荐使用不共享数据的并行测试,数据共享越多,造成状态不一致的风险就越大,因此建议有限制地使用测试数据共享。

请注意,XUnit 不会统计注入对象的初始化时间,而且多次运行测试时间会有一些区别,因此本文中列出的时间仅供参考。

拓展阅读

如果觉得自带的方注入方式满足不了你的要求,那么可以考虑使用第三方类库实现的支持 XUnit 的依赖注入容器。请关注这个项目: pengweiqhca/Xunit.DependencyInjection: Use Microsoft.Extensions.DependencyInjection to resolve xUnit test cases. (github.com)文章来源地址https://www.toymoban.com/news/detail-438112.html

到了这里,关于XUnit数据共享与并行测试的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 实战指南:使用 xUnit.DependencyInjection 在单元测试中实现依赖注入【完整教程】

    上一篇我们创建了一个 Sample.Api 项目和 Sample.Repository ,并且带大家熟悉了一下 Moq 的概念,这一章我们来实战一下在 xUnit 项目使用依赖注入。 Xunit.DependencyInjection 是一个用于 xUnit 测试框架的扩展库,它提供了依赖注入的功能,使得在编写单元测试时可以更方便地进行依赖注

    2024年04月15日
    浏览(45)
  • springboot单元测试问题解决 (空指针异常:NullPointerException 或者注入问题)

    在对springboot进行单元测试时,遇到了空指针异常 一般来说加一个注解就可以了: 1,spring版本和test版本一致 2,启动类上要加@mapperScan(“包名”)才可以注入mapper层,否则注入数据层报空指针 3,启动类上要加@ComponentScan(“父包名”)才可以扫描到service层,否则注入业务层报空指

    2024年02月16日
    浏览(45)
  • 自定义xunit测试用例的执行顺序

    有的时候我们会对程序进行单元测试, 为了测试的效果以及后期的维护, 我一般会将各个测试拆开, 根据需要测试的类分到各个类型中, 不过在实际操作的时候就出现了一些意想不到的问题, 各个测试的执行是乱序的, 按照我自己写测试的习惯, 假如我需要测试新写的增删改查的

    2024年02月05日
    浏览(49)
  • 测试框架pytest教程(7)实现 xunit 风格的setup

    pytest支持setup和teardown,对于使用unittest和nose框架的用户来说对这些很熟悉,但是在pytest可以使用功能更强大的fixture来实现固定装置。 如果单个模块中有多个测试函数和测试类,您可以选择实现以下固定方法,这些方法通常会为所有函数调用一次: 在调用类的所有测试方法之

    2024年02月11日
    浏览(36)
  • 实战指南:使用 xUnit 和 ASP.NET Core 进行集成测试【完整教程】

    集成测试可在包含应用支持基础结构(如数据库、文件系统和网络)的级别上确保应用组件功能正常。 ASP.NET Core 通过将单元测试框架与测试 Web 主机和内存中测试服务器结合使用来支持集成测试。 集成测试与单元测试相比,能够在更广泛的级别上评估应用的组件,确认多个

    2024年04月22日
    浏览(44)
  • mock写单元测试和查数据库的单元测试

    一:mock方式 在测试类上添加注解 将需要测试的类bean添加进来,该类中的其他bean也添加进来 给被测试类中用到的参数、返回值类创建对象 创建BeforeEach和AfterEach方法,在BeforeEach方法中给参数,返回值设置值 然后在test方法中设置被测试的方法 二:可以检测dao层sql的单元测试

    2024年02月15日
    浏览(56)
  • 关于前端或者postman传递Date数据测试接口报错

    错误: org.apache.ibatis.exceptions.PersistenceException: rn### Error querying database. Cause: java.lang.IllegalArgumentException: invalid comparison: java.util.Date and java.lang.Stringrn### Cause: java.lang.IllegalArgumentException: invalid comparison: java.util.Date and java.lang.String 解决: 将xml文件修改即可,然后前端或者postma

    2024年01月21日
    浏览(49)
  • 单元测试与数据库

    1.单元测试应该是全自动执行的,而非交互式的,应使用assert语句来验证结果而不是sout后进行人眼验证 2.为了保证单元测试可靠且便于维护,单元测试用例之间不能互相调用 3.单元测试是可重复执行的,不能受到外界环境的影响 4.单元测试代买必须写在src/test/java的工程目录下,这样

    2024年02月08日
    浏览(39)
  • 简化Java单元测试数据

    EasyModeling 是我在2021年圣诞假期期间开发的一个 Java 注解处理器,采用 Apache-2.0 开源协议。它可以帮助 Java 单元测试的编写者快速构造用于测试的数据模型实例,简化 Java 项目在单元测试中准备测试数据的工作,在提高编写效率的同时,使单元测试更加整洁易读。经过一年的

    2024年02月15日
    浏览(38)
  • 数据库系统概论-00引言

    -数据库系统的发展经历了三代演变 层次/网状数据库系统,关系数据库系统,新一代数据库系统。 -造就了四位图灵奖得主 C.W.Bachman,E.F.Codd,James Gray,M.R.Stoebraker.。 -发展了一门计算机基础学科· 数据建模和DBMS核心技术为主,内容丰富,领域宽广。 -带动了一个巨大软件产业 D

    2024年01月22日
    浏览(56)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包