NUnit单元测试框架:从安装到高级特性
简介:NUnit是一个为.NET框架设计的广泛使用的开源单元测试工具,提供了一种便捷的方式来验证代码是否按预期工作。本文将详细介绍NUnit的安装、基本使用、断言、测试固定、参数化测试以及更多高级特性,旨在帮助开发者全面掌握NUnit,提升代码质量和测试效率。
1. NUnit框架简介
NUnit框架概述
NUnit 是一个开源的单元测试框架,广泛应用于 .NET 开发环境。它是 xUnit 测试框架的一个分支,设计用于支持重构和递增开发。NUnit 采用 Attributes 的方式来标记测试代码,使得测试类和测试方法的识别变得简单直观。通过使用断言,NUnit 可以验证代码的输出是否符合预期,为开发者提供了一种有效的方式来确保代码的正确性和质量。
NUnit 特点
NUnit 为开发人员提供了一系列强大的特性,例如:
- 测试套件(Suites): 可以组织相关的测试用例,便于管理和运行。
- 参数化测试: 支持用不同的输入数据重复执行同一个测试,方便测试用例的复用。
- 并行测试: 可以同时运行多个测试用例,显著提高测试效率。
- 丰富的断言: 提供各种断言来验证不同的测试条件,支持异常和超时测试。
- 易于集成: 可以轻松集成到各种持续集成工具中,如 Jenkins、TeamCity 等。
NUnit 的适用场景
作为一款成熟的单元测试工具,NUnit 特别适用于对 .NET 应用程序的单元测试。它适用于各种规模的项目,从简单的类库到大型的多层企业级应用。通过 NUnit,开发人员能够保证每次代码的修改或重构都不会破坏现有的功能,从而增强软件的可靠性和稳定性。无论是在个人项目中进行快速的单元测试,还是在团队环境中集成到持续集成流程中,NUnit 都能提供强大的支持。
NUnit 的主要优势在于它的灵活性、扩展性和社区支持,它不断地更新和改进,以适应不断变化的开发实践和需求。接下来的章节中,我们将详细介绍 NUnit 的安装与配置,并深入探讨其使用方法和高级特性。
2. NUnit的安装与环境配置
2.1 NUnit安装步骤详解
2.1.1 选择合适的安装方式
NUnit作为一个.NET框架下的单元测试工具,它支持多种安装方式,以满足不同用户的需求。用户可以通过NuGet包管理器、命令行或者图形界面安装NUnit。在Visual Studio集成开发环境中,推荐使用NuGet包管理器安装NUnit。这种方式的好处是简单、快捷,而且能够保证项目中测试代码的依赖关系得到正确管理。
以下是通过NuGet安装NUnit的步骤:
- 打开你的.NET项目。
- 在Visual Studio中,点击“工具”菜单,选择“NuGet包管理器”然后是“程序包管理器控制台”。
- 在程序包管理器控制台中输入安装命令:
Install-Package NUnit
如果你还需要安装NUnit的图形界面测试运行器,则可以追加命令:
Install-Package NUnit3TestAdapter
如果你的项目基于.NET Core或.NET 5/6,那么可能需要使用以下命令:
Install-Package NUnit3TestAdapter -Version latest -PreRelease
如果你更喜欢命令行工具,也可以使用dotnet CLI命令进行安装:
dotnet add package NUnit
dotnet add package NUnit3TestAdapter --version latest --pre
对于那些喜欢图形用户界面的用户,可以通过NuGet包管理器图形界面进行安装。在Visual Studio中,通过“工具”->“NuGet包管理器”->“管理解决方案的NuGet包”,搜索NUnit,然后进行安装。
2.1.2 安装过程中的常见问题及解决方案
在安装NUnit的过程中,可能会遇到一些常见问题。例如,安装包可能会出现版本冲突,或者.NET Core支持问题。此时,解决方案通常包括以下几个方面:
- 确保使用了正确版本的NuGet包管理器,与你的.NET项目版本相匹配。
- 如果遇到版本冲突,可以尝试清理NuGet缓存,通过NuGet包管理器控制台输入以下命令进行清理:
nuget locals all -clear - 对于.NET Core支持问题,确保NUnit版本是支持你的.NET Core版本的。在安装时可以指定版本号,例如:
Install-Package NUnit -Version 3.12.0 - 如果在安装过程中遇到权限问题,可以尝试以管理员身份运行Visual Studio或命令行工具。
2.2 NUnit环境配置
2.2.1 配置.NET开发环境
为了使用NUnit,你需要在你的计算机上配置.NET开发环境。如果你使用的是Visual Studio,它已经内置了.NET支持。对于其他IDE或者代码编辑器,可能需要安装.NET SDK(软件开发工具包)。
以下是.NET环境配置的基本步骤:
- 下载并安装最新版本的.NET SDK(或者.NET Core SDK)。
- 设置环境变量,确保
dotnet命令可以在命令行中被识别。 - 如果使用Visual Studio,确保通过Visual Studio安装器安装对应的.NET工作负载。
- 验证安装,通过命令行运行
dotnet --version来检查.NET SDK版本。
2.2.2 NUnit与IDE集成
NUnit与IDE的集成是提高开发效率的重要步骤。Visual Studio、Rider以及Visual Studio Code等流行的IDE都支持NUnit集成。集成NUnit后,你可以直接在IDE中创建测试项目、运行测试并查看结果。
- Visual Studio:通过NuGet安装NUnit包后,Visual Studio会自动配置测试资源管理器,显示NUnit测试项目的运行结果。
- Rider:Rider IDE自带对NUnit的支持,安装后即可开始测试。
- Visual Studio Code:虽然VS Code本身不直接集成NUnit,但你可以通过安装扩展插件如C# for Visual Studio Code (powered by OmniSharp)来获得对NUnit的支持。
2.2.3 配置文件与环境变量设置
配置文件和环境变量对于测试的可移植性和一致性至关重要。NUnit通常会查找名为 nunit-console.exe.config 的配置文件,以获取额外的配置信息。
- 创建
nunit-console.exe.config文件,通常放在与测试可执行文件相同的目录中。你可以在这个文件中配置日志记录、代理设置和其他.NET运行时参数。 - 环境变量的配置则更为主观,取决于测试对环境的依赖。例如,你可能需要设置数据库连接字符串、应用程序配置文件路径等。
一个典型的 nunit-console.exe.config 文件配置示例如下:
<?xml version="1.0" encoding="UTF-8" ?>
<configuration>
<appSettings>
<add key="DbConnectionString" value="Data Source=myDatabaseServer;Initial Catalog=myDatabaseName;Integrated Security=True"/>
</appSettings>
</configuration>
在这个配置文件中,我们为测试项目配置了一个数据库连接字符串。
在环境变量中,你可以使用如下命令设置环境变量(以Windows为例):
setx DbConnectionString "Data Source=myDatabaseServer;Initial Catalog=myDatabaseName;Integrated Security=True"
设置完成后,这些变量会在命令行会话中自动可用,并可被NUnit测试项目读取。
这样,NUnit的安装与环境配置章节就完成了,接下来的章节我们将深入NUnit的基本测试结构和用法。
3. NUnit基本测试结构
在软件开发的持续集成和持续交付流程中,自动化测试框架扮演着至关重要的角色。NUnit作为.NET平台下的一个单元测试框架,它提供了一套简单而强大的API来编写和运行测试。在深入了解NUnit的高级特性之前,我们必须掌握其基本的测试结构。本章将从测试类和测试方法的创建开始,逐步深入到测试夹具的使用、测试生命周期的管理,以及如何组织和运行测试。
3.1 测试类和测试方法
NUnit使用属性(Attribute)来标识测试相关的类和方法。要创建一个NUnit测试类,你需要使用 [TestFixture] 属性标记一个普通的.NET类。而在这样的测试类中,你将使用 [Test] 属性来标识具体的测试方法。
3.1.1 创建测试类
让我们从一个简单的例子开始。首先,在Visual Studio或其他.NET支持的IDE中创建一个新的项目或类库。接着,创建一个新的类并添加以下代码:
using NUnit.Framework;
[TestFixture]
public class MathTests
{
// 测试方法将放在这里
}
上面的类 MathTests 被 [TestFixture] 属性标记,这告诉NUnit这将是一个测试类。请记住,NUnit的测试类必须具有一个无参数的公共构造函数。
3.1.2 编写测试方法
在 [TestFixture] 类内,我们可以定义一个或多个测试方法。测试方法同样需要被 [Test] 属性标记。这里是一个简单的测试方法的示例:
[Test]
public void AddTest()
{
Assert.AreEqual(4, Calculator.Add(2, 2));
}
在这个例子中, AddTest 方法使用了 Assert.AreEqual 方法来验证 Calculator 类的 Add 方法的输出。当 Add 方法返回的值不等于4时,测试将失败。请注意,NUnit提供了多种断言方法,用于不同类型的比较和条件测试。
flowchart LR
A[开始测试] --> B[创建测试类]
B --> C[标记类为TestFixture]
C --> D[创建测试方法]
D --> E[标记方法为Test]
E --> F[使用Assert方法进行验证]
测试类和测试方法是NUnit测试结构的基本构件。它们允许开发者以声明性的方式定义测试逻辑,并且NUnit运行器能够自动地发现并执行这些测试。
3.2 测试夹具与测试生命周期
NUnit的测试夹具(Fixture)概念是指一组测试的集合。这可以理解为在同一个测试类中的所有测试方法的组合。每个测试夹具实例化一次即完成一次完整的测试生命周期。在NUnit中,测试夹具生命周期包括了三个阶段:Arrange(准备)、Act(执行)和Assert(断言),也被广泛称作AAA模式。
3.2.1 TestFixture的使用
如前所述, [TestFixture] 属性用于标记一个类作为测试夹具。当测试运行器执行这个测试夹具时,它会经历生命周期的各个阶段。通常,每个阶段都会对应一个方法或方法集合。NUnit提供了一些特殊的属性来帮助我们更有效地管理测试夹具的生命周期。
[TestFixture]
public class MyTestFixture
{
[SetUp]
public void Setup()
{
// Arrange 阶段:进行测试前的准备工作
}
[TearDown]
public void Teardown()
{
// Teardown 阶段:进行测试后的清理工作
}
[Test]
public void TestMethod()
{
// Act & Assert 阶段:执行测试逻辑并验证结果
}
}
在上面的代码片段中, [SetUp] 和 [TearDown] 属性分别标记了执行夹具级设置和清理的方法。
3.2.2 Arrange-Act-Assert模式
Arrange-Act-Assert(AAA)模式是编写清晰测试的一个关键实践,特别是在面向行为的测试中。NUnit不仅鼓励,而且通过其API支持这一模式。AAA模式将测试逻辑分成三个部分:
- Arrange(安排) :设置测试所需的环境和输入,比如初始化对象、设置测试数据等。
- Act(执行) :调用被测试的方法或功能。
- Assert(断言) :验证执行的结果是否符合预期。
[Test]
public void DivideTest()
{
// Arrange
var calculator = new Calculator();
int a = 10;
int b = 2;
int expected = 5;
// Act
int actual = calculator.Divide(a, b);
// Assert
Assert.AreEqual(expected, actual);
}
在这个测试方法中,我们首先创建了 Calculator 对象并设置了需要的输入值(Arrange)。然后我们调用了 Divide 方法(Act)。最后我们验证了方法的返回值是否符合预期(Assert)。
3.2.3 Setup与TearDown方法
在NUnit中, [SetUp] 属性标记的方法在每个测试方法执行之前运行,这有助于我们避免代码重复。相似地, [TearDown] 属性标记的方法将在每个测试方法执行后运行,无论是成功还是失败,这通常用于清理资源或记录测试结果。
[TestFixture]
public class MyTestFixture
{
private TestContext context;
private string testResource;
[SetUp]
public void Setup()
{
// 初始化测试资源
testResource = "Initial value";
context = new TestContext();
context.WriteLine("Test setup completed");
}
[TearDown]
public void Teardown()
{
// 清理资源
if (testResource != null)
{
testResource = null;
context.WriteLine("Test teardown completed");
}
}
[Test]
public void TestMethod()
{
// 执行测试
// 由于存在SetUp和TearDown,此处无需额外资源管理代码
}
}
在这个例子中,我们在 SetUp 方法中进行了测试的初始化,并在 TearDown 方法中清理了资源。这样,每个测试方法都能以一致和干净的状态运行。
测试夹具和AAA模式共同构成了NUnit测试的基础架构。通过它们,可以编写出结构良好、易于维护和理解的测试用例。下一节我们将深入探讨如何组织测试以及如何管理测试的生命周期。
4. NUnit断言机制
在单元测试中,断言是必不可少的。NUnit通过其断言机制,让开发者能够对代码进行验证,确保它们按预期工作。本章节将深入探讨NUnit的基本断言方法和高级断言技巧。
4.1 基本断言方法
4.1.1 Assert类的方法介绍
NUnit的Assert类是测试断言的核心。它包含了一系列用于验证期望结果的方法。最基本的断言方法包括:
-
Assert.AreEqual(expected, actual):验证两个对象是否相等。 -
Assert.AreEqual(expected, actual, tolerance):验证两个浮点数是否在指定的容差范围内相等。 -
Assert.IsTrue(condition):验证条件是否为真。 -
Assert.IsNullOrEmpty(collection):验证集合是否为空或null。
除了上述方法外,Assert类还提供了很多其他断言方法,如 Assert.Contains 、 Assert.Null 、 Assert.ReferenceEquals 等,覆盖了常见的验证场景。
4.1.2 异常与超时测试
在单元测试中,检查代码是否能够正确处理异常和超时也是重要的测试点。
- 异常测试 :可以使用
Assert.Throws()方法来验证是否抛出了特定类型的异常:
```csharp
[Test]
public void TestMethodThatThrowsException()
{
var ex = Assert.Throws
(() => Divide(1, 0));
Assert.AreEqual(“Attempted to divide by zero.”, ex.Message);
}
private int Divide(int numerator, int denominator)
{
return numerator / denominator; // This will throw DivideByZeroException
}
```
上述代码中,我们验证了 Divide 方法在传入分母为0时是否正确地抛出了 DivideByZeroException 异常,并且还检查了异常消息是否符合预期。
- 超时测试 :在异步测试中,有时需要验证操作是否在预定的时间内完成。可以使用
Assert.Warn()方法来设置警告时间:
csharp [Test] [Timeout(2000)] // 设置超时时间为2000毫秒 public void TestMethodWithTimeout() { // Your async code here }
如果代码在2秒钟内没有完成,则测试将失败并给出警告。
4.2 高级断言技巧
4.2.1 使用Constraints进行断言
Constraints为断言提供了一种更为灵活和强大的方式。它们可以链式组合,形成复杂的验证逻辑。例如:
[Test]
public void ConstraintsExample()
{
int result = Factorial(5);
Assert.That(result, Is.EqualTo(120));
Assert.That(result, Is.Not.EqualTo(121));
Assert.That(result, Is.GreaterThanOrEqualTo(120));
}
在上述示例中,我们使用了 Is.EqualTo 、 Is.Not.EqualTo 和 Is.GreaterThanOrEqualTo 这样的Constraints来进行断言。
4.2.2 自定义断言方法
尽管NUnit提供了丰富的断言方法,但在某些特定情况下,我们可能需要编写自定义的断言逻辑。可以通过实现 IExpectation接口 来定义一个新的断言方法:
[Test]
public void CustomAssertionExample()
{
int actual = 5;
Assert.That(actual, CustomAssertion.Satisfies(new Condition<int>(i => i > 3, "value should be greater than 3")));
}
public static class CustomAssertion
{
public static SatisfiesConstraint Satisfies(Condition condition)
{
return new SatisfiesConstraint(condition);
}
}
public class Condition<T>
{
private readonly Func<T, bool> predicate;
private readonly string message;
public Condition(Func<T, bool> predicate, string message)
{
this.predicate = predicate;
this.message = message;
}
public bool IsSatisfiedBy(T value)
{
return predicate(value);
}
}
public class SatisfiesConstraint : Constraint
{
private readonly Condition condition;
public SatisfiesConstraint(Condition condition)
{
this.condition = condition;
this.Description = $"satisfies {condition}";
}
public override bool Matches(object actual)
{
if (condition.IsSatisfiedBy((T)actual))
{
return true;
}
else
{
Message = condition.message;
return false;
}
}
}
在这个自定义断言的例子中,我们定义了一个名为 Condition 的类,用于封装测试条件。然后,我们创建了一个实现了 IExpectation 接口的 SatisfiesConstraint 类来应用这个条件并报告结果。通过这种方式,可以为NUnit断言机制添加新的功能。
断言是单元测试的核心,NUnit通过其灵活多样的断言机制,使得测试变得更加精确和强大。开发者可以根据测试的具体需求,选择最合适的断言方法来确保代码质量。
5. NUnit测试固定管理
在现代软件开发流程中,随着功能的不断扩展和变更,测试用例的数量也会相应地增长。为了有效地管理这些测试用例,NUnit提供了一套测试套件(Test Fixtures)管理机制,这能够帮助测试人员组织和运行相关的测试用例。同时,NUnit还提供了控制测试执行顺序和保证测试隔离性的功能,以确保测试的可靠性和有效性。
5.1 测试套件的创建与管理
测试套件是NUnit中组织和运行一组测试用例的方式。通过测试套件,测试人员可以将相关的测试用例分组,并且能够单独运行这些组或运行全部测试。
5.1.1 组织测试用例
组织测试用例是为了更好地管理测试工作流,确保测试用例能够针对不同的测试目标进行分类和分组。
在NUnit中,我们通常使用 [TestFixture] 属性来标记一个测试类为测试套件。每个测试套件可以包含多个测试方法,这些测试方法使用 [Test] 属性来标记。
[TestFixture]
public class MyFirstTestSuite
{
[Test]
public void TestMethod1()
{
// 测试代码
}
[Test]
public void TestMethod2()
{
// 测试代码
}
}
一旦定义了测试套件,就可以使用NUnit的测试运行器来运行整个套件或单个测试方法。此外,还可以对测试套件进行更细致的管理,比如指定运行特定的测试套件或方法,并排除那些不相关的测试。
5.1.2 运行测试套件
运行测试套件是测试工作流程中非常重要的一步。NUnit提供了一个图形化的测试运行器,也可以通过命令行或IDE集成来运行测试套件。
在Visual Studio中,测试套件可以通过测试资源管理器运行。用户可以右键点击测试套件类或方法,然后选择“Run Tests”来执行它们。命令行的方式提供了更多的灵活性,可以通过指定类名、测试名等参数来运行特定的测试。
nunit3-console.exe /test=MyFirstTestSuite.TestMethod1
上述命令将会执行名为 TestMethod1 的测试方法。
5.2 测试运行顺序与隔离
测试的运行顺序和隔离性在确保测试一致性和可靠性方面扮演着关键角色。测试运行顺序不当可能会导致依赖关系的冲突,而测试隔离则可以防止测试间相互影响。
5.2.1 控制测试运行的顺序
在NUnit中,通常情况下测试是按照任意顺序执行的。但在某些情况下,控制测试的执行顺序是必要的,比如在测试间存在依赖关系时。
在NUnit 3中,可以通过 [Order] 属性来控制测试的运行顺序。这个属性允许你为测试方法指定一个整数值,数值越小表示该测试的优先级越高,将会被优先执行。
[TestFixture]
public class MyOrderedTestSuite
{
[Test, Order(1)]
public void FirstTest()
{
// 测试代码
}
[Test, Order(2)]
public void SecondTest()
{
// 测试代码
}
}
5.2.2 测试隔离的实现
为了防止测试之间相互影响,NUnit提供了几种机制来实现测试隔离,其中包括测试夹具的 [OneTimeSetUp] 和 [OneTimeTearDown] 属性。
[OneTimeSetUp] 属性标记的方法会在任何测试开始之前执行一次,并且是在创建任何测试夹具实例之前。同理, [OneTimeTearDown] 属性标记的方法会在所有测试完成后执行一次。
[TestFixture]
public class MyTestIsolationSuite
{
private Assembly assembly;
[OneTimeSetUp]
public void Setup()
{
assembly = Assembly.Load("myAssembly");
}
[Test]
public void Test1()
{
// 测试代码,使用 assembly
}
[Test]
public void Test2()
{
// 测试代码,使用 assembly
}
[OneTimeTearDown]
public void Teardown()
{
assembly = null;
}
}
通过这种方式,我们可以确保每个测试运行之前和之后都有一些初始化和清理的工作,而这些工作只需要进行一次,从而避免了测试间的相互干扰。
NUnit框架提供的测试套件管理和运行顺序及隔离机制,让测试人员能够有效地组织和运行大量的测试用例。这不仅提升了测试过程的效率,同时也保障了测试质量的可控性。随着我们进一步深入,第六章将介绍NUnit的高级特性与执行方法,如参数化测试、并行测试等,这些都是高级测试实践中的重要组成部分。
6. NUnit高级特性与执行方法
6.1 参数化测试与理论测试
NUnit参数化测试提供了一种机制,允许你对同一测试方法使用不同的输入参数进行多次测试。这种方法在测试函数的通用性时非常有用。
6.1.1 参数化测试的原理与应用
参数化测试使用 TestCase 属性来指定一组输入参数,以及可选的预期结果,这些参数将被用来多次执行测试方法。下面的代码展示了如何使用 TestCase 属性进行参数化测试。
[TestFixture]
public class MyTests
{
[TestCase(1, 2, 3)]
[TestCase(4, 5, 9)]
public void SumTest(int a, int b, int expected)
{
var calculator = new Calculator();
Assert.That(calculator.Sum(a, b), Is.EqualTo(expected));
}
}
通过上述代码,我们创建了一个测试用例,分别测试了两个整数相加的结果。每次测试都将创建 Calculator 类的实例,并验证 Sum 方法的输出是否与预期值匹配。
6.1.2 理论测试框架的使用
理论测试框架允许我们验证代码中的一般规则,也称为数据驱动的属性测试。使用理论测试,你可以指定一个或多个理论属性(假设),NUnit将自动生成测试用例,用不同的参数值来测试这些属性。
[TestFixture]
public class CalculatorTests
{
[Theory]
[InlineData(1, 2, 3)]
[InlineData(-1, -1, -2)]
public void SumTheoryTest(int a, int b, int expectedSum)
{
var calculator = new Calculator();
Assert.That(calculator.Sum(a, b), Is.EqualTo(expectedSum));
}
}
上述代码定义了一个理论测试,它将通过使用 InlineData 属性提供的不同参数值,测试 Sum 方法的正确性。NUnit将为每个数据组合运行一次测试。
6.2 测试执行策略与报告生成
6.2.1 并行测试与执行顺序
NUnit允许并行执行测试用例,这可以显著提高测试的执行效率。从 NUnit 3.0 开始,测试用例默认在不同的线程中并行运行,但仍然可以控制测试的执行顺序。
[TestFixture, Order(1)]
public class FirstTest
{
[Test]
public void ATest()
{
// 测试逻辑...
}
}
[TestFixture, Order(2)]
public class SecondTest
{
[Test]
public void BTest()
{
// 测试逻辑...
}
}
通过给 TestFixture 和 Test 属性添加 Order 属性,我们可以指定测试执行的顺序,从而保证测试的逻辑顺序性。
6.2.2 测试结果的报告与分析
NUnit提供了一个丰富的测试结果报告,可以通过图形界面查看,也可以以XML格式导出,以便集成到持续集成系统中。例如,使用 nunit3-console 运行测试并导出XML结果:
nunit3-console YourTestAssembly.dll /xml=TestResults.xml
生成的 TestResults.xml 文件将包含所有测试的详细信息,包括成功、失败以及忽略的测试。
6.3 集成与扩展
6.3.1 NUnit与其他测试工具的集成
NUnit可以与其他测试工具集成,比如使用TestRail进行测试用例管理和报告。要实现这一集成,通常需要使用NUnit的事件系统来捕获测试运行的事件,并将相关信息发布到TestRail。
6.3.2 扩展NUnit功能的方法与实践
NUnit允许开发者通过编写插件来扩展其功能。扩展NUnit的功能涉及编写自定义的属性、约束、或者运行器。创建一个新的属性来标记测试,可以在运行时提供额外的行为。
[TestFixture]
public class CustomTestAttribute
{
[CustomTest]
public void TestMethod()
{
// 测试逻辑...
}
}
public class CustomTestAttribute : NUnitAttribute
{
public override void BeforeTest(TestDetails testDetails)
{
// 在测试执行前执行的逻辑
}
}
通过继承 NUnitAttribute 类并重写方法,你可以创建一个可以在测试执行前或后执行特定逻辑的自定义属性。这种灵活性使得NUnit能够适应各种复杂和特定的测试需求。
简介:NUnit是一个为.NET框架设计的广泛使用的开源单元测试工具,提供了一种便捷的方式来验证代码是否按预期工作。本文将详细介绍NUnit的安装、基本使用、断言、测试固定、参数化测试以及更多高级特性,旨在帮助开发者全面掌握NUnit,提升代码质量和测试效率。
更多推荐
所有评论(0)