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 序列化需适配工业数据(日期格式、枚举、空值处理),工具类代码如下:

// 主项目: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
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
// 主项目:质检业务逻辑(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
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 提交服务+依赖接口)
// 主项目: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 安装)。

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 核心逻辑建立“测试防护网”,提前发现代码错误,避免上线后因逻辑问题导致生产停滞或数据丢失。

posted @ 2025-12-20 13:43  clnchanpin  阅读(76)  评论(0)    收藏  举报