C# MES .NET Framework Winform 单元测试

C# MES .NET Framework Winform 单元测试

MSTest 是微软官方推出的单元测试框架,Visual Studio 内置支持(无需额外安装第三方框架)、语法简洁、兼容性强(完美适配 .NET Framework 4.5+),是 MES Winform 开发中"零成本落地单元测试"的首选方案。

本文聚焦 MSTest 的核心用法,结合 MES 工业场景(工具类、业务逻辑、依赖隔离),从"环境搭建→语法特性→实战示例→运行与排错"展开。

一、核心定位:MSTest 适合 MES 开发的原因

  1. 零配置成本:Visual Studio 自带,新建项目即可用,无需额外安装框架(适合车间项目快速迭代);
  2. 语法直观 :特性命名贴近中文逻辑(如 [TestClass] 标记测试类、[TestMethod] 标记测试方法),新手易上手;
  3. 兼容性强:完美支持 .NET Framework 4.5+、Winform 项目的核心类(实体、工具类、业务逻辑);
  4. 集成度高:与 Visual Studio 测试资源管理器深度集成,一键运行、断点调试、查看结果,开发测试无缝衔接。

核心原则:MES 单元测试仍以"隔离 UI 依赖、聚焦核心逻辑"为目标,MSTest 重点测试工具类、业务规则、数据处理,UI 控件操作(如 Button 点击)仍靠集成测试或人工验证。

二、基础准备:环境搭建与项目结构

1. 环境要求

  • Visual Studio 2017+(推荐 2019/2022,内置 MSTest 框架);
  • 主项目:.NET Framework 4.5+ Winform 项目(如 MES.WinForm);
  • 测试项目:MSTest 测试项目(.NET Framework)。

2. 环境搭建步骤

步骤 1:创建 MSTest 测试项目
  1. 解决方案右键→添加→新建项目→搜索"单元测试项目(.NET Framework) "→命名(如 MES.WinForm.MSTests);
  2. 选择 .NET Framework 版本(需与主项目一致,如 4.7.2)→创建;
  3. 右键测试项目→添加→引用→选择主项目(MES.WinForm),确保能访问主项目的核心类(实体、工具类、业务逻辑类)。
步骤 2:确认必要依赖(默认已包含)

MSTest 核心依赖会自动添加,无需手动安装:

  • MSTest.TestFramework:测试框架核心(提供特性、断言方法);
  • MSTest.TestAdapter:Visual Studio 测试资源管理器适配(支持运行测试、查看结果)。
步骤 3:测试项目结构(MES 推荐)

按"功能模块"划分测试类,与主项目结构对齐,便于维护:

复制代码
MES.WinForm.MSTests/
├─ Tools/          // 工具类测试(日志、序列化、缓存)
│  ├─ JsonHelperTests.cs    // JSON序列化工具测试(MES核心)
│  └─ CacheHelperTests.cs   // 本地缓存工具测试(断网场景)
├─ Business/       // 业务逻辑测试(质检、工单、生产数据)
│  ├─ QualityCheckTests.cs  // 质检判定逻辑测试
│  └─ WorkOrderLogicTests.cs// 工单状态流转测试
├─ Validation/     // 数据校验测试(SN码、参数范围)
│  └─ DataValidationTests.cs// SN码格式、参数合法性测试
└─ Api/            // API交互测试(模拟WebApi依赖)
   └─ ProductionApiTests.cs // 生产数据提交测试(模拟API)

三、MSTest 核心语法特性(MES 常用)

MSTest 用"特性(Attribute)"标记测试类和方法,核心特性如下(结合 MES 场景说明):

特性 作用 MES 场景应用
[TestClass] 标记类为"测试类"(必须,否则测试方法不执行) 每个功能模块对应一个测试类(如 QualityCheckTests
[TestMethod] 标记方法为"测试方法"(必须,可独立运行) 每个测试场景对应一个测试方法(如"质检合格场景""参数异常场景")
[TestInitialize] 每个测试方法执行前执行(初始化公共资源) 初始化业务逻辑类、模拟对象(如每次测试前新建 QualityCheckLogic
[TestCleanup] 每个测试方法执行后执行(释放资源) 关闭模拟的设备连接、清空临时文件(MES 场景少用,因多为无状态测试)
[DataRow] 数据驱动测试(一次运行多组参数) 质检规则的多组阈值测试(如温度 20℃、30℃、31℃)
[ExpectedException] 验证方法是否抛出指定异常(兼容旧版本,推荐用 Assert.ThrowsException 验证 SN 码为空时抛出异常

核心断言方法(验证测试结果):

  • Assert.IsTrue/IsFalse:验证布尔值(如质检是否合格);
  • Assert.AreEqual:验证值相等(如 SN 码、结果描述、数值);
  • Assert.IsNotNull/IsNullOrEmpty:验证对象/字符串非空(如序列化结果);
  • Assert.ThrowsException<T>:验证抛出指定类型异常(如参数异常);
  • Assert.Inconclusive:标记测试未完成(临时用)。

四、MES 核心测试场景(MSTest 实战示例)

以下示例基于 MES 真实场景,覆盖"工具类、业务逻辑、依赖隔离"三大核心模块,直接复用即可落地。

场景 1:工具类测试(JSON 序列化工具,MES 数据交互核心)

主项目工具类(JsonHelper.cs

MES 中 JSON 序列化需适配工业数据(日期格式、枚举、空值处理),工具类代码如下:

csharp 复制代码
// 主项目:JSON序列化工具类(适配MES场景)
using Newtonsoft.Json;
using System.Collections.Generic;

namespace MES.WinForm.Tools
{
    public static class JsonHelper
    {
        // MES专属序列化配置(日期格式统一、枚举转中文、忽略空值)
        private static readonly JsonSerializerSettings _mesSettings = new JsonSerializerSettings
        {
            DateFormatString = "yyyy-MM-dd HH:mm:ss",
            Converters = new List<JsonConverter> { new StringEnumConverter() },
            NullValueHandling = NullValueHandling.Ignore
        };

        /// <summary>
        /// 序列化:C#对象→JSON字符串
        /// </summary>
        public static string Serialize(object obj)
        {
            if (obj == null)
                throw new ArgumentNullException(nameof(obj), "序列化对象不能为空(MES数据交互)");
            return JsonConvert.SerializeObject(obj, _mesSettings);
        }

        /// <summary>
        /// 反序列化:JSON字符串→C#对象
        /// </summary>
        public static T Deserialize<T>(string json)
        {
            if (string.IsNullOrEmpty(json))
                throw new ArgumentException("JSON字符串不能为空(MES数据交互)", nameof(json));
            try
            {
                return JsonConvert.DeserializeObject<T>(json, _mesSettings);
            }
            catch (Exception ex)
            {
                throw new InvalidOperationException($"JSON反序列化失败(MES数据交互):{ex.Message}", ex);
            }
        }
    }

    // MES实体类(生产记录)
    public enum ProductionStatus { 待生产, 生产中, 已完成 }
    public class ProductionRecord
    {
        public string SnCode { get; set; } // 产品SN码(追溯核心)
        public string OrderNo { get; set; } // 订单号
        public ProductionStatus Status { get; set; } // 生产状态(枚举)
        public DateTime ProductionTime { get; set; } // 生产时间
    }
}
MSTest 测试类(JsonHelperTests.cs
csharp 复制代码
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MES.WinForm.Tools;
using MES.WinForm.Entities;
using Newtonsoft.Json;

// MSTestV2核心特性:标记测试类
[TestClass]
public class JsonHelperTests
{
    // 测试场景1:正常序列化(验证日期格式、枚举格式、字段正确性)
    [TestMethod]
    public void Serialize_ValidProductionRecord_ReturnsCorrectJson()
    {
        // 1. 准备测试数据(Arrange:初始化输入和预期结果)
        var testRecord = new ProductionRecord
        {
            SnCode = "SN20251121001",
            OrderNo = "WO2025001",
            Status = ProductionStatus.生产中,
            ProductionTime = new DateTime(2025, 11, 21, 9, 30, 0)
        };
        var expectedDate = "2025-11-21 09:30:00"; // 预期日期格式
        var expectedStatus = "\"Status\":\"生产中\""; // 预期枚举序列化结果

        // 2. 执行测试方法(Act:调用要测试的方法)
        string actualJson = JsonHelper.Serialize(testRecord);

        // 3. 验证结果(Assert:判断实际结果是否符合预期)
        Assert.IsNotNullOrEmpty(actualJson, "序列化结果不能为空");
        Assert.IsTrue(actualJson.Contains(expectedDate), "日期格式不符合MES要求(yyyy-MM-dd HH:mm:ss)");
        Assert.IsTrue(actualJson.Contains(expectedStatus), "枚举序列化未转为中文名称");
        Assert.IsTrue(actualJson.Contains($"\"SnCode\":\"{testRecord.SnCode}\""), "SN码未正确序列化");
    }

    // 测试场景2:序列化空对象→抛出ArgumentNullException
    [TestMethod]
    public void Serialize_NullObject_ThrowsArgumentNullException()
    {
        // Act + Assert:验证抛出指定异常
        var exception = Assert.ThrowsException<ArgumentNullException>(
            () => JsonHelper.Serialize(null), 
            "序列化空对象未抛出异常"
        );
        Assert.AreEqual("序列化对象不能为空(MES数据交互)", exception.Message, "异常信息不一致");
        Assert.AreEqual("obj", exception.ParamName, "异常参数名错误");
    }

    // 测试场景3:正常反序列化(验证JSON→对象的正确性)
    [TestMethod]
    public void Deserialize_ValidJson_ReturnsProductionRecord()
    {
        // Arrange:准备符合MES格式的JSON字符串
        string testJson = @"{
            ""SnCode"":""SN20251121002"",
            ""OrderNo"":""WO2025002"",
            ""Status"":""已完成"",
            ""ProductionTime"":""2025-11-21 10:00:00""
        }";
        var expectedRecord = new ProductionRecord
        {
            SnCode = "SN20251121002",
            OrderNo = "WO2025002",
            Status = ProductionStatus.已完成,
            ProductionTime = new DateTime(2025, 11, 21, 10, 0, 0)
        };

        // Act
        var actualRecord = JsonHelper.Deserialize<ProductionRecord>(testJson);

        // Assert
        Assert.IsNotNull(actualRecord, "反序列化对象为空");
        Assert.AreEqual(expectedRecord.SnCode, actualRecord.SnCode, "SN码不匹配");
        Assert.AreEqual(expectedRecord.Status, actualRecord.Status, "生产状态不匹配");
        Assert.AreEqual(expectedRecord.ProductionTime, actualRecord.ProductionTime, "生产时间不匹配");
    }

    // 测试场景4:无效JSON→抛出InvalidOperationException
    [TestMethod]
    public void Deserialize_InvalidJson_ThrowsInvalidOperationException()
    {
        // Arrange:无效JSON(缺少右括号)
        string invalidJson = @"{
            ""SnCode"":""SN20251121003"",
            ""OrderNo"":""WO2025003""
        ";

        // Act + Assert
        var exception = Assert.ThrowsException<InvalidOperationException>(
            () => JsonHelper.Deserialize<ProductionRecord>(invalidJson),
            "无效JSON未抛出反序列化异常"
        );
        Assert.IsTrue(exception.Message.Contains("JSON反序列化失败(MES数据交互)"), "异常信息不符合预期");
        Assert.IsInstanceOfType(exception.InnerException, typeof(JsonReaderException), "内部异常类型错误");
    }
}

场景 2:业务逻辑测试(质检判定,MES 核心规则)

MES 质检逻辑直接影响产品判定结果,需严格测试"正常流程、异常参数、边界值"。

主项目业务类(QualityCheckLogic.cs
csharp 复制代码
// 主项目:质检业务逻辑(MES核心规则:温度20-30℃、压力3.0-4.0MPa→合格)
namespace MES.WinForm.Business
{
    public class QualityCheckLogic
    {
        /// <summary>
        /// 产品质检判定
        /// </summary>
        /// <param name="snCode">产品SN码(必填)</param>
        /// <param name="temperature">温度(0-100℃,超出范围视为无效)</param>
        /// <param name="pressure">压力(0-10MPa,超出范围视为无效)</param>
        /// <returns>质检结果</returns>
        public QualityResult CheckQuality(string snCode, decimal temperature, decimal pressure)
        {
            // 1. 参数校验(MES场景:必填字段+合理范围校验)
            if (string.IsNullOrWhiteSpace(snCode))
                throw new ArgumentException("SN码不能为空(质检判定)", nameof(snCode));
            if (temperature < 0 || temperature > 100)
                throw new ArgumentOutOfRangeException(nameof(temperature), "温度超出合理范围(0-100℃)");
            if (pressure < 0 || pressure > 10)
                throw new ArgumentOutOfRangeException(nameof(pressure), "压力超出合理范围(0-10MPa)");

            // 2. 执行质检规则
            bool isQualified = temperature >= 20 && temperature <= 30 
                              && pressure >= 3.0m && pressure <= 4.0m;

            // 3. 返回结果(包含追溯信息)
            return new QualityResult
            {
                SnCode = snCode,
                IsQualified = isQualified,
                ResultDesc = isQualified ? "合格" : $"不合格(温度:{temperature}℃,压力:{pressure}MPa)",
                CheckTime = DateTime.Now
            };
        }
    }

    // 质检结果实体
    public class QualityResult
    {
        public string SnCode { get; set; }
        public bool IsQualified { get; set; }
        public string ResultDesc { get; set; }
        public DateTime CheckTime { get; set; }
    }
}
MSTest 测试类(QualityCheckTests.cs
csharp 复制代码
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MES.WinForm.Business;

[TestClass]
public class QualityCheckTests
{
    private QualityCheckLogic _qualityLogic;

    // 每个测试方法执行前初始化(避免重复创建对象)
    [TestInitialize]
    public void TestInitialize()
    {
        _qualityLogic = new QualityCheckLogic(); // 初始化业务逻辑类
    }

    // 测试场景1:参数正常(温度25℃、压力3.5MPa)→ 合格
    [TestMethod]
    public void CheckQuality_ValidParams_ReturnsQualified()
    {
        // Arrange
        string snCode = "SN20251121004";
        decimal temperature = 25.0m;
        decimal pressure = 3.5m;

        // Act
        var result = _qualityLogic.CheckQuality(snCode, temperature, pressure);

        // Assert
        Assert.IsTrue(result.IsQualified, "符合质检规则但判定为不合格");
        Assert.AreEqual("合格", result.ResultDesc, "合格结果描述错误");
        Assert.AreEqual(snCode, result.SnCode, "SN码追溯信息错误");
    }

    // 测试场景2:温度超标(32℃)→ 不合格
    [TestMethod]
    public void CheckQuality_TemperatureOverMax_ReturnsUnqualified()
    {
        // Arrange
        string snCode = "SN20251121005";
        decimal temperature = 32.0m; // 超出上限(30℃)
        decimal pressure = 3.5m;

        // Act
        var result = _qualityLogic.CheckQuality(snCode, temperature, pressure);

        // Assert
        Assert.IsFalse(result.IsQualified, "温度超标但判定为合格");
        Assert.AreEqual($"不合格(温度:32.0℃,压力:3.5MPa)", result.ResultDesc, "不合格结果描述错误");
    }

    // 测试场景3:SN码为空→ 抛出ArgumentException
    [TestMethod]
    public void CheckQuality_EmptySnCode_ThrowsArgumentException()
    {
        // Act + Assert
        var exception = Assert.ThrowsException<ArgumentException>(
            () => _qualityLogic.CheckQuality("", 25.0m, 3.5m),
            "SN码为空未抛出异常"
        );
        Assert.AreEqual("SN码不能为空(质检判定)", exception.Message);
    }

    public static IEnumerable<object[]> QualityTestData() {
            yield return new object[] { "SN006", 20.0m, 3.0m, true, "合格" };
            yield return new object[] { "SN007", 30.0m, 4.0m, true, "合格" };
            yield return new object[] { "SN008", 19.9m, 3.5m, false, "不合格(温度:19.9℃,压力:3.5MPa)" };
            yield return new object[] { "SN009", 25.0m, 2.9m, false, "不合格(温度:25.0℃,压力:2.9MPa)" };
            yield return new object[] { "SN010", 30.1m, 3.5m, false, "不合格(温度:30.1℃,压力:3.5MPa)" };
        }

        // 数据驱动测试:一次测试多组参数(高效覆盖边界值、异常值)
        [DynamicData(nameof(QualityTestData),DynamicDataSourceType.Method)]
        [TestMethod]
        public void CheckQuality_DataDriven_Tests(string snCode, decimal temp, decimal pressure, bool expectedQualified, string expectedDesc)
        {
            // Act
            var result = _qualityLogic.CheckQuality(snCode, temp, pressure);

            // Assert
            Assert.AreEqual(expectedQualified, result.IsQualified, $"SN:{snCode} 判定结果错误");
            Assert.AreEqual(expectedDesc, result.ResultDesc, $"SN:{snCode} 结果描述错误");
        }
     }

场景 3:依赖隔离测试(模拟 WebApi,MES 外部依赖场景)

MES 常依赖 WebApi、PLC 设备、数据库,单元测试需用 Moq 模拟这些依赖(避免真实调用),仅测试自身逻辑。

主项目核心代码(API 提交服务+依赖接口)
csharp 复制代码
// 主项目:API交互接口(便于模拟)
namespace MES.WinForm.Api
{
    public interface IApiClient
    {
        // 提交生产数据到WebApi
        Task<ApiResponse> SubmitProductionDataAsync(ProductionRecord record);
    }

    // API响应实体
    public class ApiResponse
    {
        public bool Success { get; set; }
        public string Msg { get; set; }
    }

    // 缓存接口(便于模拟)
    public interface ICacheHelper
    {
        // 本地缓存失败数据(断网场景)
        void CacheFailedData(ProductionRecord record);
    }

    // 生产数据提交服务(依赖 IApiClient 和 ICacheHelper)
    public class ProductionDataService
    {
        private readonly IApiClient _apiClient;
        private readonly ICacheHelper _cacheHelper;

        // 构造函数注入依赖(便于测试时替换为模拟对象)
        public ProductionDataService(IApiClient apiClient, ICacheHelper cacheHelper)
        {
            _apiClient = apiClient ?? throw new ArgumentNullException(nameof(apiClient));
            _cacheHelper = cacheHelper ?? throw new ArgumentNullException(nameof(cacheHelper));
        }

        /// <summary>
        /// 提交生产数据:成功返回true;失败/异常则缓存数据,返回false
        /// </summary>
        public async Task<bool> SubmitDataAsync(ProductionRecord record)
        {
            if (record == null) throw new ArgumentNullException(nameof(record));
            if (string.IsNullOrWhiteSpace(record.SnCode)) throw new ArgumentException("SN码不能为空");

            try
            {
                var apiResponse = await _apiClient.SubmitProductionDataAsync(record);
                if (apiResponse.Success)
                {
                    LogHelper.WriteLog($"SN:{record.SnCode} 提交成功");
                    return true;
                }
                else
                {
                    LogHelper.WriteLog($"SN:{record.SnCode} 提交失败:{apiResponse.Msg}");
                    _cacheHelper.CacheFailedData(record); // 缓存失败数据
                    return false;
                }
            }
            catch (Exception ex)
            {
                LogHelper.WriteLog($"SN:{record.SnCode} 提交异常:{ex.Message}");
                _cacheHelper.CacheFailedData(record); // 异常时也缓存
                return false;
            }
        }
    }
}
MSTest 测试类(ProductionDataServiceTests.cs,结合 Moq)

需先安装 Moq 包(右键测试项目→管理 NuGet 程序包→搜索 Moq 安装)。

csharp 复制代码
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MES.WinForm.Api;
using MES.WinForm.Entities;
using Moq;
using System.Threading.Tasks;

[TestClass]
public class ProductionDataServiceTests
{
    private ProductionDataService _dataService;
    private Mock<IApiClient> _mockApiClient; // 模拟IApiClient
    private Mock<ICacheHelper> _mockCacheHelper; // 模拟ICacheHelper
    private ProductionRecord _testRecord; // 测试用生产记录

    // 每个测试方法执行前初始化
    [TestInitialize]
    public void TestInitialize()
    {
        // 1. 初始化模拟对象
        _mockApiClient = new Mock<IApiClient>();
        _mockCacheHelper = new Mock<ICacheHelper>();

        // 2. 初始化服务(注入模拟对象)
        _dataService = new ProductionDataService(_mockApiClient.Object, _mockCacheHelper.Object);

        // 3. 初始化测试数据
        _testRecord = new ProductionRecord
        {
            SnCode = "SN20251121011",
            OrderNo = "WO2025011",
            Status = ProductionStatus.已完成,
            ProductionTime = DateTime.Now
        };
    }

    // 测试场景1:API提交成功→返回true,不缓存
    [TestMethod]
    public async Task SubmitDataAsync_ApiSuccess_ReturnsTrue()
    {
        // Arrange:设置模拟API返回成功
        _mockApiClient.Setup(api => api.SubmitProductionDataAsync(_testRecord))
            .ReturnsAsync(new ApiResponse { Success = true, Msg = "提交成功" });

        // Act
        bool result = await _dataService.SubmitDataAsync(_testRecord);

        // Assert
        Assert.IsTrue(result, "API提交成功但返回false");
        // 验证API被调用1次
        _mockApiClient.Verify(api => api.SubmitProductionDataAsync(_testRecord), Times.Once, "API未被调用");
        // 验证缓存未被调用(成功无需缓存)
        _mockCacheHelper.Verify(cache => cache.CacheFailedData(It.IsAny<ProductionRecord>()), Times.Never, "成功场景不应缓存");
    }

    // 测试场景2:API返回失败→返回false,缓存数据
    [TestMethod]
    public async Task SubmitDataAsync_ApiFailed_ReturnsFalseAndCache()
    {
        // Arrange:设置模拟API返回失败
        _mockApiClient.Setup(api => api.SubmitProductionDataAsync(_testRecord))
            .ReturnsAsync(new ApiResponse { Success = false, Msg = "SN码重复" });

        // Act
        bool result = await _dataService.SubmitDataAsync(_testRecord);

        // Assert
        Assert.IsFalse(result, "API提交失败但返回true");
        // 验证缓存被调用1次
        _mockCacheHelper.Verify(cache => cache.CacheFailedData(_testRecord), Times.Once, "失败场景未缓存数据");
    }

    // 测试场景3:API调用异常(断网)→返回false,缓存数据
    [TestMethod]
    public async Task SubmitDataAsync_ApiException_ReturnsFalseAndCache()
    {
        // Arrange:设置模拟API抛出异常(断网场景)
        _mockApiClient.Setup(api => api.SubmitProductionDataAsync(_testRecord))
            .ThrowsAsync(new HttpRequestException("网络连接失败"));

        // Act
        bool result = await _dataService.SubmitDataAsync(_testRecord);

        // Assert
        Assert.IsFalse(result, "API异常但返回true");
        // 验证缓存被调用1次
        _mockCacheHelper.Verify(cache => cache.CacheFailedData(_testRecord), Times.Once, "异常场景未缓存数据");
    }

    // 测试场景4:传入空记录→抛出ArgumentNullException
    [TestMethod]
    public async Task SubmitDataAsync_NullRecord_ThrowsArgumentNullException()
    {
        // Act + Assert
        var exception = await Assert.ThrowsExceptionAsync<ArgumentNullException>(
            () => _dataService.SubmitDataAsync(null),
            "传入空记录未抛出异常"
        );
        Assert.AreEqual("record", exception.ParamName, "异常参数名错误");
    }
}

五、MSTest 测试运行与结果查看(Visual Studio 操作)

1. 运行测试

  1. 打开"测试资源管理器":视图→测试→测试资源管理器(快捷键:Ctrl+E, T);
  2. 构建解决方案:确保主项目和测试项目无编译错误(Ctrl+Shift+B);
  3. 运行测试:
    • 运行单个测试:右键测试方法→运行;
    • 运行所有测试:点击测试资源管理器顶部"全部运行";
    • 运行选中测试:选中多个测试方法→右键→运行。

2. 查看结果

  • 绿色对勾(✅):测试通过;
  • 红色叉号(❌):测试失败(点击失败项,下方会显示异常信息、堆栈跟踪);
  • 黄色警告(⚠):测试未完成(如标记 [Inconclusive])。

3. 断点调试测试

在测试方法或主项目代码中设置断点→右键测试方法→调试选定的测试,即可像调试业务代码一样排查问题。

六、MES 场景 MSTest 最佳实践(避坑指南)

  1. 优先测试核心逻辑,不做"无用测试"

    • 必测:工具类(序列化、缓存)、业务规则(质检、工单流转)、数据校验(SN 码、参数范围);
    • 不测:简单 getter/setter、第三方库功能(如 Newtonsoft.Json 本身)、UI 控件交互。
  2. 异常分支覆盖>正常流程

    MES 工业环境异常频发(断网、设备离线、数据错误),重点测试:

    • 参数异常(空值、超出范围、格式错误);
    • 外部依赖异常(API 超时、设备通信失败);
    • 业务边界值(如质检阈值 20℃、30℃,测试 19.9℃、20℃、30℃、30.1℃)。
  3. 依赖隔离是关键

    • 不真实连接 PLC、WebApi、数据库,用 Moq 模拟接口依赖;
    • 主项目代码设计为"依赖注入"(通过接口注入,而非硬编码 new ApiClient()),便于测试时替换模拟对象。
  4. 测试数据贴合工业场景

    • 用真实 MES 数据格式(如 SN 码为"SN+日期+序号"、订单号为"WO+年份+序号");
    • 避免用随机测试数据(如 SN 码用"test123"),确保测试与实际生产数据兼容。
  5. 避免测试方法依赖

    • 每个测试方法独立(不共享静态变量、临时文件),确保"单独运行通过,批量运行也通过";
    • [TestInitialize] 初始化公共资源,[TestCleanup] 释放资源(如关闭模拟连接)。

七、总结:MSTest 落地 MES 单元测试的核心

MSTest 最适合 MES Winform 开发的核心优势是"低门槛、高集成"------无需额外配置,新建项目即可测试,完美契合车间项目"快速落地、稳定可靠"的需求。

核心步骤:

  1. 新建 MSTest 测试项目,引用主项目;
  2. [TestClass]/[TestMethod] 标记测试类和方法;
  3. 按"Arrange-Act-Assert"三段式编写测试逻辑;
  4. Moq 模拟外部依赖(API、设备),隔离测试环境;
  5. 重点覆盖业务规则和异常场景,保障工业级稳定性。

通过 MSTest,可快速为 MES 核心逻辑建立"测试防护网",提前发现代码错误,避免上线后因逻辑问题导致生产停滞或数据丢失。

相关推荐
裤裤兔3 小时前
利用VBA批处理word 文档,使用宏对docx文件内容进行批量替换
c#·word·.net··vba·office·宏操作
2501_941149503 小时前
Java高性能微服务异步通信与Kafka/RabbitMQ实战分享:分布式消息优化与系统解耦经验
c#·linq
mudtools6 小时前
使用.NET 8+ 与飞书API构建组织架构同步服务
架构·.net·飞书
gc_22996 小时前
学习C#调用FreeSpire.Doc包将Word转换为html
c#·html·word·freespire.doc
娶不到胡一菲的汪大东6 小时前
C# 泛型 委托 接口
开发语言·windows·c#
ceclar1237 小时前
C#常用集合的使用
开发语言·windows·c#
N***73857 小时前
ReactGraphQLAPI
大数据·c#·爬山算法
lingxiao168887 小时前
WPF Prism框架应用
c#·wpf·prism
IMPYLH9 小时前
Lua 的 pairs 函数
开发语言·笔记·后端·junit·单元测试·lua