XUnit单元测试(实用项目)——看完不会用你打我

news2024/11/24 19:33:36

一、简介

xUnit.net 是针对 .NET 的免费、开源单元测试框架,可并行测试、数据驱动测试。测试项目需要同时引用 xUnit和被测试项目,从而对其进行测试。测试编写完成后,用 Test Runner 来测试项目,Test Runner 可以读取测试代码,并且知道所会使用的测试框架,然后执行,并显示结果。

二、支持平台

xUnit.net 目前支持 .Net Framework、.Net Core、.Net Standard、UWP、Xamarin ,可以在这些平台使用 xUnit 进行测试。

三、核心思想

单元测试的核心思想:万物皆虚拟(mock data)、测试某个类时要假定其他类都正常、单元测试代码和被测试代码的目录结构最好一致。
配置项是虚拟的,配置项各属性的值要重新设置。
类实例是虚拟的,类实例中的不同方法返回什么结果要提前设置。
Http请求是虚拟的,不同http请求返回什么结果要提前设置。

四、XUnit具体用法详解

欲写单元测试首先得有要测试的功能/服务,写服务的过程就不在这里赘述了,写单元测试的时候我会把对应的服务关键代码截图过来让大家对照着看,下面正式开始:

1、准备环境

1.1新建项目

1.2安装依赖项及项目结构概览(FluentAssertions、Moq)

2、对AnalyzerService.cs进行单元测试

先来个简单点的对AnalyzerService.cs进行单元测试,具体可参照下述步骤:

  • 创建对应的测试类AnalyzerServiceUnitTest.cs
  • 声明测试对象IAnalyzerService _sut(System Under Test待测系统的缩写)
  • 在构造方法中创建AnalyzerService实例并赋值给测试对象,创建实例所需的参数都要用mock数据,缺哪些就声明哪些
  • 声明并用new对mock数据初始化后还要对其中的属性/方法进行设置才能使用(呼应第三点核心思想,这也是单元测试最重要的一步)
  • 测试对象创建完毕后,要根据IAnalyzerService创建测试方法(单元测试要包含其中的所有方法,甚至更多)
  • 单一方法业务较复杂时可以根据if条件将其拆分成多个测试方法,所以测试方法可能多于接口原有的方法
  • 若原方法有返回值可以根据返回值是否符合预期来进行断言,若原方法没返回值只要不报错即可,若需要判断异常我这也有处理方法
  • 下面直接上代码,我会尽可能的添加注释帮助大家理解

namespace LearnUnitTest.Test.Services
{
    public class AnalyzerServiceUnitTest
    {
        private readonly string _token;
        private readonly string _fileMetadataId;
        //声明测试对象
        private readonly IAnalyzerService _sut;
        //声明new AnalyzerService()所需的参数
        private readonly Mock<ISauthService> _sauthService;
        private readonly Mock<IFileMetadataService> _fileMetadataService;
        private readonly Mock<IProcessTimeService> _processTimeService;
        private readonly Mock<IErrorRecordService> _errorRecordService;
        private readonly Mock<IOptions<TimeSettings>> _timeSettings;
        private readonly Mock<IOptions<ReferencingServices>> _referencingServices;
        private readonly Mock<IOptions<ErrorRecordSetting>> _errorRecordSetting;
        private readonly Mock<IActivityService> _activityService;
        private readonly Mock<ILogger<AnalyzerService>> _logger;

        public AnalyzerServiceUnitTest()
        {
            _token = Guid.NewGuid().ToString();
            _fileMetadataId = "UT_FileMetadataId";
            _sauthService = new Mock<ISauthService>();
            _fileMetadataService = new Mock<IFileMetadataService>();
            _processTimeService = new Mock<IProcessTimeService>();
            _errorRecordService = new Mock<IErrorRecordService>();
            _timeSettings = new Mock<IOptions<TimeSettings>>();
            _referencingServices = new Mock<IOptions<ReferencingServices>>();
            _errorRecordSetting = new Mock<IOptions<ErrorRecordSetting>>();
            _activityService = new Mock<IActivityService>();
            _logger = new Mock<ILogger<AnalyzerService>>();
            //mock数据创建实例后还要根据需求对其中的属性/方法进行设置才能使用
            InitOptions();
            InitServices();

            //创建AnalyzerService实例并赋值给测试对象
            _sut = new AnalyzerService(_sauthService.Object, _fileMetadataService.Object,
                _processTimeService.Object, _errorRecordService.Object,
                _timeSettings.Object, _referencingServices.Object, _errorRecordSetting.Object,
                _activityService.Object, _logger.Object);
        }

        [Fact]
        public async Task Analyze_ShouldSuccess_WhenHasLatestProcessTime()
        {
            LatestProcessTimeRecord latestProcessTimeRecord = new LatestProcessTimeRecord()
            {
                //设置返回值非空
                LatestProcessTime = DateTime.UtcNow
            };
            //AnalyzerService中GetLatestProcessTimeAsync()返回空和非空会进行不同的逻辑处理,所以将其拆分成两个方法
            //通过重写GetLatestProcessTimeAsync()的返回值对不同的情况进行逻辑覆盖
            _processTimeService.Setup(x=>x.GetLatestProcessTimeAsync(It.IsAny<string>()))
                .ReturnsAsync(latestProcessTimeRecord);

            await _sut.AnalyzeAsync();
        }

        [Fact]
        public async Task Analyze_ShouldSuccess_WhenNoLatestProcessTime()
        {
            LatestProcessTimeRecord latestProcessTimeRecord = new LatestProcessTimeRecord()
            {
                //设置返回值为空
                LatestProcessTime = null
            };
            //AnalyzerService中GetLatestProcessTimeAsync()返回空和非空会进行不同的逻辑处理,所以将其拆分成两个方法
            //通过重写GetLatestProcessTimeAsync()的返回值对不同的情况进行逻辑覆盖
            _processTimeService.Setup(x => x.GetLatestProcessTimeAsync(It.IsAny<string>()))
                .ReturnsAsync(latestProcessTimeRecord);

            await _sut.AnalyzeAsync();
        }

        [Fact]
        public async Task NoImplement_ShouldThrowException_Method01()
        {
            Func<Task> result = async () => await _sut.NoImplementAsync(_token);
            //链式进行异常判断
            await result.Should().ThrowAsync<NotImplementedException>();
        }

        [Fact]
        public async Task NoImplement_ShouldThrowException_Method02()
        {
            try
            {
                //try...catch进行异常判断
                await _sut.NoImplementAsync(_token);
                Assert.True(false);
            }
            catch (NotImplementedException ex)
            {
                Assert.True(true);
            }
            catch
            {
                Assert.True(false);
            }
        }

        private void InitOptions()
        {
            TimeSettings timeSettings = new TimeSettings()
            {
                DefaultUploadTime = "2022-06-01T00:00:00.000Z"
            };
            //IOptions<T>类型的变量通过Value属性来获取实际的参数值,所以这里要重写Value属性
            _timeSettings.Setup(x => x.Value).Returns(timeSettings);

            ReferencingServices referencingServices = new ReferencingServices()
            {
                DataPartitionId = "DataPartition-Id",
                FileServiceURL = "https://global.FileService.URL",
                ActivityServiceURL = "https://global.ActivityService.URL",
                ProcessStatusServiceURL = "https://global.ProcessStatusService.URL"
            };
            _referencingServices.Setup(x => x.Value).Returns(referencingServices);

            ErrorRecordSetting errorRecordSetting = new ErrorRecordSetting()
            {
                DefaultMaxRetryCount = 5
            };
            _errorRecordSetting.Setup(x => x.Value).Returns(errorRecordSetting);
        }

        private void InitServices()
        {
            //AnalyzerService中调用了ISauthService.GetToken(),不重写GetToken()则所有用到此方法的地方返回值都是null(返回值是string类型)
            _sauthService.Setup(x => x.GetToken()).ReturnsAsync(_token);

            List<FileMetadataGetResponse> fileMetadatas = new List<FileMetadataGetResponse>()
            {
                new FileMetadataGetResponse() { Id = _fileMetadataId }
            };
            //当需要重写的方法有参数时,根据参数类型用It.IsAny<T>()代替即可
            _fileMetadataService.Setup(x => x.QueryMetadatasAsync(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<string>()))
                .ReturnsAsync(fileMetadatas);

            WellTestingEmission wellTestingEmission = new WellTestingEmission()
            {
                Id = _fileMetadataId,
                CreatedTime = DateTime.UtcNow,
                Emissions = new List<EmissionInfo>()
                {
                    new EmissionInfo()
                    {
                        Name = "CO2EstimatedEmissionForGas",
                        Unit = "T",
                        Value = 0.0f
                    }
                }
            };
            _fileMetadataService.Setup(x => x.GetFileContentByMetadataIdAsync(It.IsAny<string>(), It.IsAny<string>()))
                .ReturnsAsync(wellTestingEmission);

            _errorRecordService.Setup(x => x.InsertProcessErrorAsync(It.IsAny<ErrorBaseRecord>(), It.IsAny<string>(), It.IsAny<string>()));

            _errorRecordService.Setup(x => x.UpdateProcessErrorAsync(It.IsAny<ErrorBaseRecord>(), It.IsAny<string>(), It.IsAny<string>()));

            _activityService.Setup(x => x.InsertOrUpdateActivityAsync(It.IsAny<string>(), It.IsAny<WellTestingEmission>(), It.IsAny<OperationalActivity>(), It.IsAny<string>()));
        }
    }
}

3、对ActivityService.cs进行单元测试

本节在AnalyzerServiceUnitTest.cs单元测试的基础上添加了对http请求的处理(直接用System.Net.Http.HttpClient发送Post/Get请求),单元测试不能发送真实的http请求,所以我们要对不同的http请求分别设置对应的返回值(mock数据),具体可参照下述步骤:

  • 创建对应的测试类ActivityServiceUnitTest.cs
  • 声明测试对象IActivityService _sut(System Under Test待测系统的缩写)
  • 在构造方法中创建ActivityService实例并赋值给测试对象,创建实例所需的参数都要用mock数据,缺哪些就声明哪些
  • 声明并用new对mock数据初始化后还要对其中的属性/方法进行设置才能使用(呼应第三点核心思想,这也是单元测试最重要的一步)
  • 测试对象创建完毕后,要根据IActivityService创建测试方法(单元测试要包含其中的所有方法,甚至更多)
  • 单一方法业务较复杂时可以根据if条件将其拆分成多个测试方法,所以测试方法可能多于接口原有的方法
  • 若原方法有返回值可以根据返回值是否符合预期来进行断言,若原方法没返回值只要不报错即可,若需要判断异常我这也有处理方法(参考上一节
  • 下面直接上代码,我会尽可能的添加注释帮助大家理解

namespace LearnUnitTest.Test.Services
{
    public class ActivityServiceUnitTest
    {
        private readonly string _token;
        private readonly string _fileMetadataId;
        //多次用到的对象要定义成全局变量
        private readonly WellTestingEmission _wellTestingEmission;
        private readonly OperationalActivity _operationalActivity;
        private readonly JsonSerializerOptions _jsonSerializerOptions;
        //声明new ActivityService()所需的参数
        private readonly IActivityService _sut;
        private readonly Mock<ISauthService> _sauthService;
        private readonly Mock<IOptions<ReferencingServices>> _referencingServices;
        private readonly Mock<ILogger<ActivityService>> _logger;

        //重写http请求所需的变量
        private Mock<HttpMessageHandler> _httpMessageHandler;
        private Mock<IHttpClientFactory> _httpClientFactory;
        private IHttpClientWrapperService _httpClientWrapperSvc;
        private Mock<ILogger<HttpClientWrapperService>> _httpClientLogger;

        public ActivityServiceUnitTest()
        {
            _token = Guid.NewGuid().ToString();
            _fileMetadataId = "UT_FileMetadataId";
            _jsonSerializerOptions = new JsonSerializerOptions()
            {
                PropertyNamingPolicy = JsonNamingPolicy.CamelCase
            };
            _sauthService = new Mock<ISauthService>();
            _referencingServices = new Mock<IOptions<ReferencingServices>>();
            _logger = new Mock<ILogger<ActivityService>>();
            _wellTestingEmission = new WellTestingEmission()
            {
                JobId = "UT_JobId",
                JobName = "UT_JobName",
                CountryOfOrigin = "UT_CountryOfOrigin",
                CustomerName = "UT_CustomerName",
                FdpNumber = "UT_FdpNumber"
            };
            _operationalActivity = new OperationalActivity()
            {
                Wells = new[] { new Well() { Wellname = "UT_Wellname01", Wellfield = "UT_Wellfield01" } }
            };
            //mock数据创建实例后还要根据需求对其中的属性/方法进行设置才能使用
            InitOptions();
            InitServices();
            InitHttpClient();

            _sut = new ActivityService(_sauthService.Object, _httpClientWrapperSvc,
                _referencingServices.Object, _logger.Object);
        }

        [Fact]
        public async Task ExtractActivity_ShouldSuccess()
        {
            var result = _sut.ExtractActivity(_fileMetadataId, _wellTestingEmission, _operationalActivity);
            //判断响应结果是否正确时要逐层判断,避免空指针异常
            result.Data.Should().NotBeNull();
            result.Data.Execution.Should().NotBeNull();
            result.Data.Execution.Id.Should().NotBeNull();
            result.Data.Execution.Id.Should().Be(_wellTestingEmission.JobId);
            result.Data.Details.Should().NotBeNull();
            result.Data.Details.MetadataIds.Should().HaveCountGreaterThan(0);
            result.Data.Details.MetadataIds[0].Should().Be(_fileMetadataId);
        }

        [Fact]
        public async Task InsertOrUpdateActivity_ShouldSuccess_WhenInsert()
        {
            ResponseBase<ActivityBatchQueryResponse> activityBatchQueryResponse = new ResponseBase<ActivityBatchQueryResponse>() 
            {
                Data = new ActivityBatchQueryResponse()
                {
                    //TotalCount<=0
                    TotalCount = 0
                }
            };
            ResponseBase<ActivityCreateResponse> activityCreateResponse = new ResponseBase<ActivityCreateResponse>()
            {
                Data = new ActivityCreateResponse() { Id= _wellTestingEmission.JobId }
            };
            //AnalyzerService中调用http://xxx/activities/query返回值response.Data.TotalCount是否大于0会进行不同的逻辑处理,所以将其拆分成两个方法
            //通过重写http请求的返回值对不同的情况进行逻辑覆盖
            SetHttpMessageHandlerSendAsyncMock(activityBatchQueryResponse, activityCreateResponse);
            await _sut.InsertOrUpdateActivityAsync(_fileMetadataId, _wellTestingEmission, _operationalActivity, _token);
        }

        [Fact]
        public async Task InsertOrUpdateActivity_ShouldSuccess_WhenUpdate()
        {
            ResponseBase<ActivityBatchQueryResponse> activityBatchQueryResponse = new ResponseBase<ActivityBatchQueryResponse>()
            {
                Data = new ActivityBatchQueryResponse()
                {
                    //TotalCount>0且Results集合不为空
                    TotalCount = 1,
                    Results = new List<ActivityBatchQueryItem>() { new ActivityBatchQueryItem() { Id = _wellTestingEmission.JobId } }
                }
            };
            SetHttpMessageHandlerSendAsyncMock(activityBatchQueryResponse, null);
            await _sut.InsertOrUpdateActivityAsync(_fileMetadataId, _wellTestingEmission, _operationalActivity, _token);
        }

        private void InitOptions()
        {
            ReferencingServices referencingServices = new ReferencingServices() 
            {
                DataPartitionId = "DataPartition-Id",
                FileServiceURL = "https://global.FileService.URL",
                ActivityServiceURL = "https://global.ActivityService.URL",
                ProcessStatusServiceURL = "https://global.ProcessStatusService.URL"
            };
            //IOptions<T>类型的变量通过Value属性来获取实际的参数值,所以这里要重写Value属性
            _referencingServices.Setup(x => x.Value).Returns(referencingServices);
        }

        private void InitServices()
        {
            //AnalyzerService中调用了ISauthService.GetToken(),不重写GetToken()则所有用到此方法的地方返回值都是null(返回值是string类型)
            _sauthService.Setup(x => x.GetToken()).ReturnsAsync(_token);
        }

        /// <summary>
        /// 这里只重写了IHttpClientFactory.CreateClient(string name),具体根据不同的请求返回不同的响应结果要在测试方法中设置
        /// </summary>
        private void InitHttpClient()
        {
            _httpClientFactory = new Mock<IHttpClientFactory>();
            _httpMessageHandler = new Mock<HttpMessageHandler>();
            _httpClientLogger = new Mock<ILogger<HttpClientWrapperService>>();
            _httpClientWrapperSvc = new HttpClientWrapperService(_httpClientFactory.Object, _httpClientLogger.Object);
            //IHttpClientFactory创建HttpClient有两个重载:CreateClient()和CreateClient(string name)
            //但CreateClient()最终也是调用了CreateClient(string name),所以应该重写CreateClient(string name)
            _httpClientFactory.Setup(x => x.CreateClient(It.IsAny<string>())).Returns(() => new HttpClient(_httpMessageHandler.Object));
        }

        private void SetHttpMessageHandlerSendAsyncMock(ResponseBase<ActivityBatchQueryResponse> activityBatchQueryResponse, ResponseBase<ActivityCreateResponse> activityCreateResponse)
        {
            HttpResponseMessage httpResponseMsg = new()
            {
                StatusCode = HttpStatusCode.OK
            };
            _httpMessageHandler.Protected()
                .Setup<Task<HttpResponseMessage>>("SendAsync", ItExpr.IsAny<HttpRequestMessage>(), ItExpr.IsAny<CancellationToken>())
                .Returns(async (HttpRequestMessage request, CancellationToken token) =>
                {
                    //根据request.RequestUri中的关键字来判断接口的作用,进而设置对应的响应结果
                    if (request.RequestUri.AbsolutePath.Contains("activities/query", StringComparison.OrdinalIgnoreCase))
                    {
                        httpResponseMsg.Content = new StringContent(JsonSerializer.Serialize(activityBatchQueryResponse, _jsonSerializerOptions), Encoding.UTF8, "application/json");
                    }
                    else if ((request.RequestUri.AbsolutePath.Contains("activities/" + _wellTestingEmission.JobId, StringComparison.OrdinalIgnoreCase)))
                    {
                        httpResponseMsg.Content = new StringContent(_wellTestingEmission.JobId, Encoding.UTF8, "application/json");
                    }
                    else
                    {
                        httpResponseMsg.Content = new StringContent(JsonSerializer.Serialize(activityCreateResponse, _jsonSerializerOptions), Encoding.UTF8, "application/json");
                    }
                    return httpResponseMsg;
                });
        }
    }
}

五、总结

完成上边两个单元测试之后其余Services的处理也都大同小异,这里就不完全展示了。

不同公司对单元测试的定义多多少少会有些不同,这里探讨的是xUnit+Moq数据的方式,在此之前我也看过许多博主关于单元测试的文章,但大多写的比较简单,比如:测试的方法就是个加减乘除,Assert比较一下结果是否正确就结束了,这样的文章很难应用到项目中,我也是后来接触到单元测试才有幸整理出这么一套东西,如果对大家有所帮助请点赞、关注、评论支持下,谢谢。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1163028.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

最新版付费进群源码带自动定位和分销以及分站功能完整版无加密

简介 看到别人发那些不是挂羊头卖狗肉&#xff0c;要么就是发的缺少文件引流的。非常滴恶心 这源码是我付费花钱买的免费分享给大家&#xff0c;功能完整。而且无加密 功能&#xff1a;新建分销会员&#xff0c;设置账号密码&#xff0c;收款方式等 说明&#xff1a; 分站…

opengl基础笔记1

1、opengl运行模式及opengl规范 运行模式&#xff1a;核心模式与立即渲染模式&#xff08;弃用&#xff09; 由于OpenGL的大多数实现都是由显卡厂商编写的&#xff0c;当产生一个bug时通常可以通过升级显卡驱动来解决。这些驱动会包括你的显卡能支持的最新版本的OpenGL&#xf…

算法题:99.恢复二叉搜索树

&#xff08;为不影响大家的观感&#xff0c;完整题目附在了最后&#xff09; 二叉搜索树的定义 二叉搜索树&#xff08;BST&#xff0c;Binary Search Tree&#xff09;&#xff0c;也称二叉排序树或二叉查找树。 二叉搜索树&#xff1a;一棵二叉树&#xff0c;可以为空&…

二叉树题目:在二叉树中增加一行

文章目录 题目标题和出处难度题目描述要求示例数据范围 解法一思路和算法代码复杂度分析 解法二思路和算法代码复杂度分析 题目 标题和出处 标题&#xff1a;在二叉树中增加一行 出处&#xff1a;623. 在二叉树中增加一行 难度 5 级 题目描述 要求 给定一个二叉树的根结…

WiFi模块的环境可持续性:可再生能源、材料创新与碳足迹管理

随着数字化时代的到来&#xff0c;WiFi模块已经成为我们日常生活和工作中不可或缺的一部分。然而&#xff0c;这种便利也伴随着对环境的一定影响。本文将深入研究WiFi模块在环境可持续性方面的挑战和机遇&#xff0c;重点关注可再生能源的应用、材料创新以及碳足迹管理。 1. 可…

NB-IoT水表和LoRa水表有什么区别?

在众多物联网技术中&#xff0c;NB-IoT和LoRa脱颖而出&#xff0c;广泛应用于智能水表的制造。那么&#xff0c;这两种水表究竟有何区别呢&#xff1f;下面&#xff0c;小编来为大家详细的讲下NB-IoT和LoRa这两者的工作原理以及两者的区别之处&#xff0c;一起来看下吧&#xf…

图片路径名动态生成

写成这样也可以 :src"./src/assets/ScreenLeft/btn${isShowLeft ? Show : Hide}.png"为了节省开销&#xff0c;这种小图标&#xff0c;可以用i标签 const imgUrl ref("icon1");<iclass"w-50px h-50px":style"{backgroundImage: url(./…

老师们看过来,成绩发布原来可以这么简单

成绩发布不再让你头大&#xff01;不再需要每次都要手动查找学生成绩&#xff0c;浪费宝贵的休息时间!现在&#xff0c;只需要掌握一些小技巧&#xff0c;就能轻松实现学生自助查询成绩&#xff01; 引入数据库管理软件 可以引入一些数据库管理软件&#xff0c;例如MySQL、Pos…

Scala 泛型编程

1. 泛型 Scala 支持类型参数化&#xff0c;使得我们能够编写泛型程序。 1.1 泛型类 Java 中使用 <> 符号来包含定义的类型参数&#xff0c;Scala 则使用 []。 class Pair[T, S](val first: T, val second: S) {override def toString: String first ":" sec…

高压放大器的作用和优势是什么

高压放大器是一种专门用于放大高电压信号的设备&#xff0c;它具有许多重要的作用和优势。在以下的文章中&#xff0c;我们将详细介绍高压放大器的作用和优势。 高压放大器的作用之一是在实验室和工程应用中提供对高压信号的放大和控制。许多应用领域&#xff0c;如实验物理、电…

C/C++数据结构-链表-链表合并排序输出

文章目录 前言例题题解带头结点不带头结点 前言 这个小例题涵盖的知识点还是非常多的。包括链表的定义&#xff0c;链表的尾插法&#xff0c;链表的遍历&#xff0c;冒泡排序用链表实现。链表可以使用带头结点和不带头结点的形式&#xff0c;各有千秋。 本文完整版使用带头结点…

美团圈圈私域加群app拉新 单价高一手渠道整理

美团圈圈为我们现在主推产品 可以通过”聚量推客“ 申请推广 一手数据&#xff0c;外面很多其它的数据都会阉割的很厉害&#xff0c;可以对比 下面介绍下圈圈的优势&#xff0c;我们目前圈圈对外结算时间为11天&#xff08;因为美团官方有个要求 10天内退群无效&#xff09;&…

技能证里的天花板—阿里云云计算架构师ACE认证!

在当今的社会中&#xff0c;想要获得一份好工作、得到丰厚的报酬&#xff0c;唯一的方法就是证明自己优秀&#xff0c;能给公司创造价值&#xff0c;是大多数公司想要看到的。 那么在面试过程中&#xff0c;怎么样才能让面试官一眼就记住呢&#xff1f;那一定是有一份足够优秀…

Java生成二维码并打印二维码和文字信息

目录 1、生成二维码&#xff0c;并调用画布进行二维码和文字的描绘。 2、主程序&#xff1a;获取打印机&#xff0c;并打印内容 3、打印效果 参考文献&#xff1a; 前期工作是安装好打印机驱动&#xff0c;可连接打印机。 添加三个依赖jar&#xff1a; 具体见文底的参考文献…

什么情况会造成电表不走数?

电表是衡量用电量的重要设备&#xff0c;一旦出现不走数的情况&#xff0c;不仅会影响用户对用电量的准确计算&#xff0c;还可能造成电费纠纷。那么&#xff0c;究竟什么情况下会造成电表不走数呢&#xff1f;接下来&#xff0c;小编来为大家介绍下&#xff0c;一起来看下吧&a…

学习使用php实现汉字验证码

学习使用php实现汉字验证码 <?php //开启session &#xff0c;方便验证 session_start(); //创建背景画布 $image imagecreatetruecolor(200, 60); $background imagecolorallocate($image, 255, 255, 255); imagefill($image, 0, 0, $background);//创建背景画布 for ($…

【腾讯云HAI域探秘】AI让我变成灵魂画手

【腾讯云HAI域探秘】AI让我变成灵魂画手 文章目录 【腾讯云HAI域探秘】AI让我变成灵魂画手前言环境搭建进入 StableDiffusionWebUI使用 高性能应用服务HAI 部署的 StableDiffusionWebUI 快速进行AI绘画总结 前言 最近有幸参与了腾讯云举办的 腾讯云 HAI 产品体验活动。在这个过…

解决VS中文编译时出现:常量中有换行符

错误如下 首先确定文件编码格式已经是utf-8了&#xff0c;然后在有中文的情况的下&#xff0c;编译时会报错&#xff0c;真的很恶心 tabWidget.addTab(widget, tr("材料库"));tabWidget.addTab(widget1, tr("B"));解决方案 通过在项目里设置编译选项: /utf…

【docker使用Jar自定义镜像:基于windows】

在一个空文件夹中创建Dockerfile 将jar包复制到该路径下 在Dockerfile中添加以下内容 # 指定基础镜像 FROM java:8-alpine# 和java项目的包 COPY ./study_dockerfile-1.0.0.jar /tmp/app.jar# 暴露端口 EXPOSE 8081# 入口&#xff0c;java项目的启动命令 ENTRYPOINT java -jar…