C#MSTest教程展示了如何使用MSTest框架在C#中进行单元测试。
单元测试是一种软件测试,其中测试软件的各个单元(组件)。单元测试的目的是验证软件的每个单元是否按设计执行。单元是任何软件中最小的可测试部分。
MSTest是Microsoft的单元测试库。它适用于所有.NET语言。还有其他单元测试库,包括XUnit和NUnit。
我们可以将测试放在同一个项目目录中,也可以放在不同的目录中。我们从一个更简单的选项开始,并将测试放在同一个项目目录中。最后,我们将测试放在解决方案中的不同目录中。
$ dotnet add package Microsoft.NET.Test.Sdk $ dotnet add package MSTest.TestAdapter $ dotnet add package MSTest.TestFramework
为了使用MSTest,我们需要添加这三个库。
C#MSTest简单示例
我们从一个简单的例子开始。
namespace Messages.Services; public class Messages { public static Func<string> msg1 = () => "Hello there!"; public static Func<string> msg2 = () => "Good Morning!"; }
我们测试简单的消息功能。
我们将测试放入测试目录。MSTest自动发现我们的测试。
namespace Messages.Tests; using Microsoft.VisualStudio.TestTools.UnitTesting; using Messages.Services; [TestClass] public class MessageTest { private const string Expected1 = "Hello there!"; private const string Expected2 = "Good Morning!"; [TestMethod] public void Message1() { var m1 = Messages.msg1(); Assert.AreEqual(Expected1, m1); } [TestMethod] public void Message2() { var m2 = Messages.msg2(); Assert.AreEqual(Expected2, m2); } }
类用[TestClass]
属性注释,测试方法用[TestMethod]
属性注释。我们使用断言来确保正确的输出。
$ dotnet test ... Starting test execution, please wait... A total of 1 test files matched the specified pattern. Passed! - Failed: 0, Passed: 2, Skipped: 0, Total: 2, ...
C#MSTest参数化测试
[DataTestMethod]
属性指示参数化方法。使用[DataRow]
属性添加参数。
namespace Arithmetic.Services; public class Basic { public static Func<int, int, int> add = (a, b) => a + b; public static Func<int, int, int> mul = (a, b) => a * b; public static Func<int, int, int> sub = (a, b) => a - b; public static Func<int, int, int> div = (a, b) => a / b; }
我们将测试简单的算术函数。
namespace Messages.Tests; using Microsoft.VisualStudio.TestTools.UnitTesting; using Arithmetic.Services; [TestClass] public class ArithTest { [DataTestMethod] [DataRow(1, 2, 3)] [DataRow(2, 2, 4)] [DataRow(-1, 4, 3)] public void Add(int x, int y, int expected) { int r = Basic.add(x, y); Assert.AreEqual(r, expected); } [DataTestMethod] [DataRow(1, 2, -1)] [DataRow(2, 2, 0)] [DataRow(3, 2, 1)] public void Sub(int x, int y, int expected) { int r = Basic.sub(x, y); Assert.AreEqual(r, expected); } [DataTestMethod] [DataRow(9, 3, 27)] [DataRow(3, 3, 9)] [DataRow(-3, -3, 9)] public void Mul(int x, int y, int expected) { int r = Basic.mul(x, y); Assert.AreEqual(r, expected); } [DataTestMethod] [DataRow(9, 3, 3)] [DataRow(3, 3, 1)] [DataRow(8, 2, 4)] public void Div(int x, int y, int expected) { int r = Basic.div(x, y); Assert.AreEqual(r, expected); } }
在此示例中,我们使用三组值测试每个方法。
[DataTestMethod] [DataRow(1, 2, 3)] [DataRow(2, 2, 4)] [DataRow(-1, 4, 3)] public void Add(int x, int y, int expected) { int r = Basic.add(x, y); Assert.AreEqual(r, expected); }
我们正在测试Add
方法。该方法使用[DataRow]
属性给出的三组值进行测试。将计算值和预期
值与Assert.AreEqual
断言进行比较。
C#MSTest跳过测试
可以使用[Ignore]
属性跳过测试方法。
namespace Arithmetic.Tests; using Microsoft.VisualStudio.TestTools.UnitTesting; using Arithmetic.Services; [TestClass] public class ArithTest { [DataRow(1, 2, 3)] [DataRow(2, 2, 4)] [DataRow(-1, 4, 3)] [DataTestMethod] public void Add(int x, int y, int z) { int r = Basic.add(x, y); Assert.AreEqual(r, z); } [DataTestMethod] [DataRow(1, 2, -1)] [DataRow(2, 2, 0)] [DataRow(3, 2, 1)] public void Sub(int x, int y, int z) { int r = Basic.sub(x, y); Assert.AreEqual(r, z); } [DataTestMethod] [DataRow(9, 3, 27)] [DataRow(3, 3, 9)] [DataRow(-3, -3, 9)] [Ignore] public void Mul(int x, int y, int z) { int r = Basic.mul(x, y); Assert.AreEqual(r, z); } [DataTestMethod] [DataRow(9, 3, 3)] [DataRow(3, 3, 1)] [DataRow(8, 2, 4)] [Ignore] public void Div(int x, int y, int z) { int r = Basic.div(x, y); Assert.AreEqual(r, z); } }
我们有四种测试方法。使用[Ignore]
属性跳过其中两个。
$ dotnet test ... Starting test execution, please wait... A total of 1 test files matched the specified pattern. Skipped Mul (9,3,27) Skipped Mul (3,3,9) Skipped Mul (-3,-3,9) Skipped Div (9,3,3) Skipped Div (3,3,1) Skipped Div (8,2,4) Passed! - Failed: 0, Passed: 6, Skipped: 6, Total: 12, Duration: 84 ms ...
C#MSTest动态数据
使用[DynamicData]
属性,我们可以将测试数据外化到方法或属性中。
namespace Messages.Tests; using Microsoft.VisualStudio.TestTools.UnitTesting; using Arithmetic.Services; [TestClass] public class Tests { [DataTestMethod] [DynamicData(nameof(AddData), DynamicDataSourceType.Method)] public void Add(int x, int y, int expected) { int r = Basic.add(x, y); Assert.AreEqual(r, expected); } [DataTestMethod] [DynamicData(nameof(SubData), DynamicDataSourceType.Method)] public void Sub(int x, int y, int expected) { int r = Basic.sub(x, y); Assert.AreEqual(r, expected); } [DataTestMethod] [DynamicData(nameof(MulData), DynamicDataSourceType.Method)] public void Mul(int x, int y, int expected) { int r = Basic.mul(x, y); Assert.AreEqual(r, expected); } [DataTestMethod] [DynamicData(nameof(DivData), DynamicDataSourceType.Method)] public void Div(int x, int y, int expected) { int r = Basic.div(x, y); Assert.AreEqual(r, expected); } private static IEnumerable<object[]> AddData() { return new[] { new object[] { 1, 2, 3 }, new object[] { 2, 2, 4 }, new object[] { -1, 4, 3 } }; } private static IEnumerable<object[]> SubData() { return new[] { new object[] { 1, 2, -1 }, new object[] { 2, 2, 0 }, new object[] { 3, 2, 1 } }; } private static IEnumerable<object[]> MulData() { return new[] { new object[] { 9, 3, 27 }, new object[] { 3, 3, 9 }, new object[] { -3, -3, 9 } }; } private static IEnumerable<object[]> DivData() { return new[] { new object[] { 9, 3, 3 }, new object[] { 3, 3, 1 }, new object[] { 8, 2, 4 } }; } }
在示例中,我们在不同的方法中有测试数据。
将测试放在单独的目录中
在下面的示例中,我们展示了如何将测试放在单独的目录中。
$ mkdir Separate $ cd Separate
我们创建一个新目录。
$ dotnet new sln
我们创建一个新的空解决方案。
$ mkdir PalindromeService PalindromeService.Tests
创建了两个目录。
$ cd PalindromeService $ dotnet new classlib
我们创建一个新库。
namespace Palindrome.Services; using System.Globalization; public class PalindromeService { public bool IsPalindrome(string word) { IEnumerable<string> GraphemeClusters(string s) { var enumerator = StringInfo.GetTextElementEnumerator(s); while (enumerator.MoveNext()) { yield return (string)enumerator.Current; } } var reversed = string.Join("", GraphemeClusters(word).Reverse().ToArray()); return reversed == word; } }
PalindromeService
包含IsPalindrome
方法,该方法确定一个单词是否为回文。
$ cd .. $ dotnet sln add PalindromeService\PalindromeService.csproj
我们将PalindromeService
添加到解决方案中。
$ cd PalindromeService.Tests $ dotnet new mstest $ dotnet add reference ..\PalindromeService\PalindromeService.csproj
我们转到PalindromeService.Tests
目录并添加unit
库,添加对PalindromeService
的引用。
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <TargetFramework>net6.0</TargetFramework> <Nullable>enable</Nullable> <IsPackable>false</IsPackable> </PropertyGroup> <ItemGroup> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" /> <PackageReference Include="MSTest.TestAdapter" Version="2.2.8" /> <PackageReference Include="MSTest.TestFramework" Version="2.2.8" /> </ItemGroup> <ItemGroup> <ProjectReference Include="..\PalindromeService\PalindromeService.csproj" /> </ItemGroup> </Project>
这是项目文件的样子。
namespace Palindrome.Services.Tests; using Microsoft.VisualStudio.TestTools.UnitTesting; [TestClass] public class Tests { private PalindromeService? _palindromeService; [TestInitialize] public void SetUp() { _palindromeService = new PalindromeService(); } [DataTestMethod] [DataRow("racecar")] [DataRow("level")] [DataRow("nun")] public void IsPalindrome(string word) { var r = _palindromeService!.IsPalindrome(word); Assert.AreEqual(r, true); } }
我们用三个单词测试IsPalindrome
方法。
[TestInitialize] public void SetUp() { _palindromeService = new PalindromeService(); }
[TestInitialize]
属性用于提供一组在调用每个测试方法之前执行的通用函数。在我们的例子中,我们创建了PalindromeService
。
$ cd .. $ dotnet sln add PalindromeService.Tests\PalindromeService.Tests.csproj
我们将测试项目添加到解决方案中。
$ dotnet test
最后,我们可以运行测试了。
在本文中,我们使用MSTest库在C#中完成了单元测试。
列出所有C#教程。