C#单元测试Nunit常用注解及代码示例
NUnit提供了多个注解(Attributes)用于标记测试方法和测试类,以便进行各种配置和扩展。以下是一些常用的NUnit注解及其作用和示例代码,整理出来方便学习和使用。
1.[TestFixture]
:标记一个测试类
它指示该类包含测试方法,并可以执行相关的设置和清理操作。
[TestFixture]
public class MathTests
{
// 测试方法和其他注解
}
2.[Test]
:标记一个测试方法
它表示一个单独的测试用例。
[Test]
public void Add_TwoPositiveNumbers_ReturnsCorrectSum()
{
// 测试方法的代码和断言
}
3.[SetUp]
:测试执行之前
标记一个方法,在每个测试方法之前执行准备操作。它可以用于设置共享的测试环境。
[SetUp]
public void Setup()
{
// 在每个测试方法运行之前执行的设置操作
}
4.[TearDown]
:测试执行之后
标记一个方法,在每个测试方法之后执行清理操作。它可以用于清理测试过程中产生的资源或状态。
[TearDown]
public void TearDown()
{
// 在每个测试方法运行之后执行的清理操作
}
5.[TestCase]
:测试用例
标记一个测试方法,指定参数化的测试用例。它允许传递不同的参数进行多次测试。
[Test]
[TestCase(5, 2, 3)]
[TestCase(10, 5, 5)]
public void Subtract_TwoNumbers_ReturnsCorrectDifference(int a, int b, int expectedDifference)
{
// 测试方法的代码和断言
}
6.[Category]
:标记过滤
标记一个测试方法或测试类
指定测试的分类标签。它可以用于过滤测试的运行。
[Test]
[Category("Math")]
public void Add_TwoPositiveNumbers_ReturnsCorrectSum()
{
// 测试方法的代码和断言
}
7.[Ignore]
:标记忽略
标记一个测试方法或测试类,
表示该测试被忽略,不会被执行。它可以用于临时禁用某些测试。
[Test]
[Ignore("Temporarily disabled")]
public void Add_TwoPositiveNumbers_ReturnsCorrectSum()
{
// 测试方法的代码和断言
}
8.[MaxTime]
:标记最大时间
标记一个测试方法,设置最大执行时间。如果测试方法的执行时间超过指定时间,则测试失败。
[Test]
[MaxTime(1000)]
public void Multiply_TwoNumbers_ReturnsCorrectProduct()
{
// 测试方法的代码和断言
}
9.[Retry]:标记重试次数
标记一个测试方法,设置重试次数。如果测试失败,则会自动重试指定次数。
[Test]
[Retry(3)]
public void Divide_TwoNumbers_ReturnsCorrectQuotient()
{
// 测试方法的代码和断言
}
10. [TestCaseSource]:指定测试数据源
标记一个测试方法,指定使用提供的数据源进行参数化测试。可以使用不同的数据源来动态生成测试用例。
[Test]
[TestCaseSource(nameof(GetTestData))]
public void Multiply_TwoNumbers_ReturnsCorrectProduct(int a, int b, int expectedProduct)
{
// 测试方法的代码和断言
}
private static IEnumerable<TestCaseData> GetTestData()
{
yield return new TestCaseData(2, 3, 6);
yield return new TestCaseData(5, 5, 25);
}
11. [Timeout]:标记超时
标记一个测试方法,设置执行超时时间。如果测试方法的执行时间超过指定时间,则测试失败。
[Test]
[Timeout(1000)]
public void LongRunningTest()
{
// 长时间运行的测试方法的代码和断言
}
12.[Parallelizable]:标记并行
标记一个测试类,指示该类的测试方法可以并行运行。可以提高测试的执行速度。
[TestFixture]
[Parallelizable]
public class MathTests
{
// 测试方法和其他注解
}
13.[Sequential]
:标记顺序
标记一个测试类,指示该类的测试方法按顺序执行。默认情况下,NUnit会并行执行测试方法,使用该注解可以强制按顺序执行。
[TestFixture]
[Sequential]
public class MathTests
{
// 测试方法和其他注解
}
14.[OneTimeSetUp]
:标记一次性初始
标记一个方法,在整个测试过程开始之前执行一次的准备操作。适用于设置整个测试过程中的共享环境。
[OneTimeSetUp]
public void OneTimeSetup()
{
// 在整个测试过程开始之前执行的设置操作
}
15.[OneTimeTearDown]
:标记一次性清理
标记一个方法,在整个测试过程结束之后执行一次的清理操作。适用于清理整个测试过程中产生的资源或状态。
[OneTimeTearDown]
public void OneTimeTearDown()
{
// 在整个测试过程结束之后执行的清理操作
}
16.[TestOf]
:标记测试对象
标记一个测试方法,指定要测试的方法或类的名称。用于更清晰地指示测试方法的目标。
[Test]
[TestOf(typeof(MathUtils))]
public void Add_TwoPositiveNumbers_ReturnsCorrectSum()
{
// 测试方法的代码和断言
}
17.[Range]
:指定参数范围
标记一个参数,指定参数的取值范围。可以用于生成连续的测试用例。
[Test]
public void Divide_TwoNumbers_ReturnsCorrectQuotient(
[Range(1, 10)] int a,
[Range(1, 10)] int b)
{
// 测试方法的代码和断言
}
18.[Random]
:指定参数随机
标记一个参数,指定参数的随机值。可以用于生成随机的测试用例。
[Test]
public void Multiply_TwoRandomNumbers_ReturnsCorrectProduct(
[Random(1, 10, 5)] int a,
[Random(1, 10, 5)] int b)
{
// 测试方法的代码和断言
}
19.[Values]
:指定参数
标记一个参数,指定参数的多个取值。可以用于生成离散的测试用例。
[Test]
public void Subtract_TwoNumbers_ReturnsCorrectDifference(
[Values(10, 5, 2)] int a,
[Values(2, 1, 1)] int b)
{
// 测试方法的代码和断言
}
注意 Values此标记中的值交叉映射到了 Subtract_TwoNumbers_ReturnsCorrectDifference方法的参数a和b身上,一共进行的9次离散测试。不是三次测试哦,具体解释见下面。
20.[Values]和[TestCase]的区别
[Values]
和[TestCase]
是NUnit中用于参数化测试的两个注解,它们有一些区别和适用场景:
[Values]
:[Values]
注解用于指定参数的多个取值,生成离散的测试用例。每个参数值都会作为独立的测试用例进行执行。 [Test]
public void Subtract_TwoNumbers_ReturnsCorrectDifference(
[Values(10, 5, 2)] int a,
[Values(2, 1, 1)] int b)
{
// 测试方法的代码和断言
}
上面的示例会生成9个测试用例,分别是:
- a=10, b=2
- a=10, b=1
- a=10, b=1
- a=5, b=2
- a=5, b=1
- a=5, b=1
- a=2, b=2
- a=2, b=1
- a=2, b=1
[Values]
适合于具有离散参数取值的测试场景,可以方便地生成多个测试用例,覆盖各种不同的参数组合。
[TestCase]
:[TestCase]
注解用于指定参数化的测试用例。通过传递不同的参数进行多次测试,每个参数组合都会作为一个测试用例进行执行。 [Test]
[TestCase(5, 2, 3)]
[TestCase(10, 5, 5)]
public void Subtract_TwoNumbers_ReturnsCorrectDifference(int a, int b, int expectedDifference)
{
// 测试方法的代码和断言
}
上面的示例会生成两个测试用例,分别是:
- a=5, b=2, expectedDifference=3
- a=10, b=5, expectedDifference=5
[TestCase]
适合于每个参数组合都有不同的期望结果的测试场景,可以通过指定不同的参数组合和期望结果来进行多次测试。
总结来说,[Values]
用于生成离散的参数取值的测试用例,而[TestCase]
用于指定具体的参数组合和期望结果的测试用例。根据具体的测试需求,选择适合的注解来编写参数化测试用例。
21.[TestCaseSource]
:提供测试用例数据源
[TestCaseSource]
是 NUnit 中用于参数化测试的特性之一。它允许从静态字段、属性或方法中提供测试用例数据源。
使用 [TestCaseSource]
可以灵活地定义和传递测试用例数据,从而在不修改测试方法代码的情况下执行多组测试。可以从一个数据源中动态提供测试用例。
(1)使用静态字段作为数据源
private static readonly object[] TestCases =
{
new object[] { 2, 3, 5 }, // 正常情况下的加法
new object[] { -2, 3, 1 }, // 负数相加
new object[] { 0, 0, 0 }, // 零相加
// 其他测试用例...
};
[Test]
[TestCaseSource(nameof(TestCases))]
public void AddTest(int a, int b, int expected)
{
int actual = Add(a, b);
Assert.AreEqual(expected, actual);
}
(2)使用静态属性作为数据源
private static IEnumerable TestCases => new[]
{
new TestCaseData(2, 3, 5), // 正常情况下的加法
new TestCaseData(-2, 3, 1), // 负数相加
new TestCaseData(0, 0, 0), // 零相加
// 其他测试用例...
};
[Test]
[TestCaseSource(nameof(TestCases))]
public void AddTest(int a, int b, int expected)
{
int actual = Add(a, b);
Assert.AreEqual(expected, actual);
}
(3)使用静态方法作为数据源
private static IEnumerable TestCases()
{
yield return new TestCaseData(2, 3, 5); // 正常情况下的加法
yield return new TestCaseData(-2, 3, 1); // 负数相加
yield return new TestCaseData(0, 0, 0); // 零相加
// 其他测试用例...
}
[Test]
[TestCaseSource(nameof(TestCases))]
public void AddTest(int a, int b, int expected)
{
int actual = Add(a, b);
Assert.AreEqual(expected, actual);
}
注意事项:
- 测试用例数据源可以是
object[]
、IEnumerable
、IEnumerable<object>
、IEnumerable<TestCaseData>
等类型。 - 对于每个测试用例,需要使用
TestCaseData
类的实例进行封装,以提供输入参数和预期结果。 - 数据源名称可以是数据源字段、属性或方法的名称,可以使用
nameof
运算符避免硬编码。
通过使用 [TestCaseSource]
特性,可以简化测试用例的编写,同时提供更灵活的参数化测试能力。这样可以轻松地执行多组输入输出的测试,覆盖不同的场景和边界情况。
22.示例一: MathUtilsTests
以下是一个稍微复杂一点的NUnit单元测试代码示例:
using NUnit.Framework;
[TestFixture]
public class MathUtilsTests
{
private MathUtils mathUtils;
[OneTimeSetUp]
public void OneTimeSetUp()
{
// 在整个测试过程开始之前执行的设置操作
mathUtils = new MathUtils();
}
[OneTimeTearDown]
public void OneTimeTearDown()
{
// 在整个测试过程结束之后执行的清理操作
mathUtils = null;
}
[SetUp]
public void Setup()
{
// 在每个测试方法运行之前执行的设置操作
// 可以在这里进行初始化操作或准备测试数据
}
[TearDown]
public void TearDown()
{
// 在每个测试方法运行之后执行的清理操作
// 可以在这里进行资源释放或状态重置
}
[Test]
public void Add_TwoPositiveNumbers_ReturnsCorrectSum()
{
// Arrange
int a = 5;
int b = 3;
int expectedSum = 8;
// Act
int sum = mathUtils.Add(a, b);
// Assert
Assert.AreEqual(expectedSum, sum);
}
[Test]
public void Divide_TwoNumbers_ReturnsCorrectQuotient()
{
// Arrange
int a = 10;
int b = 2;
int expectedQuotient = 5;
// Act
int quotient = mathUtils.Divide(a, b);
// Assert
Assert.AreEqual(expectedQuotient, quotient);
}
[Test]
[TestCase(5, 2, 3)]
[TestCase(10, 5, 5)]
public void Subtract_TwoNumbers_ReturnsCorrectDifference(int a, int b, int expectedDifference)
{
// Act
int difference = mathUtils.Subtract(a, b);
// Assert
Assert.AreEqual(expectedDifference, difference);
}
[Test]
[MaxTime(1000)]
public void Multiply_TwoNumbers_ReturnsCorrectProduct()
{
// Arrange
int a = 4;
int b = 5;
int expectedProduct = 20;
// Act
int product = mathUtils.Multiply(a, b);
// Assert
Assert.AreEqual(expectedProduct, product);
}
[Test]
[Retry(3)]
public void Divide_ByZero_ThrowsDivideByZeroException()
{
// Arrange
int a = 10;
int b = 0;
// Act & Assert
Assert.Throws<DivideByZeroException>(() => mathUtils.Divide(a, b));
}
}
上述示例展示了一个包含常见特性的复杂一点的NUnit单元测试代码:
- 使用了
[TestFixture]
标记测试类,并在其中进行初始化和清理操作。 - 使用了
[SetUp]
和[TearDown]
分别在每个测试方法前后执行设置和清理操作。 - 使用了
[OneTimeSetUp]
和[OneTimeTearDown]
在整个测试过程开始和结束时执行设置和清理操作。 - 使用了不同的测试方法,包括参数化测试(
[TestCase]
),最大执行时间限制([MaxTime]),以及失败重试(
[Retry]`)等。 - 使用了
Assert
断言来验证测试结果的正确性。
请根据实际需要适配这个示例,并在测试方法中编写具 体的测试逻辑和断言,以确保代码的正确性和覆盖率。
23.示例二:MainWindowTests
以下是一个更复杂和全面的WPF单元测试代码示例,涉及多个控件和功能:
using NUnit.Framework;
using Moq;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
[TestFixture]
public class MainWindowTests
{
private MainWindow mainWindow;
private Mock<IDataService> dataServiceMock;
[SetUp]
public void Setup()
{
// 创建数据服务的模拟对象
dataServiceMock = new Mock<IDataService>();
// 创建主窗口,并将模拟对象注入
mainWindow = new MainWindow(dataServiceMock.Object);
}
[Test]
public void AddItemButton_Clicked_ItemIsAddedToListBox()
{
// Arrange
string newItem = "New Item";
Button addButton = mainWindow.FindName("AddItemButton") as Button;
ListBox itemListBox = mainWindow.FindName("ItemListBox") as ListBox;
// Act
mainWindow.ItemTextBox.Text = newItem;
addButton.RaiseEvent(new RoutedEventArgs(Button.ClickEvent));
// Assert
Assert.IsTrue(itemListBox.Items.Contains(newItem));
}
[Test]
public void RemoveItemButton_Clicked_SelectedItemIsRemovedFromListBox()
{
// Arrange
string itemToRemove = "Item 1";
Button removeButton = mainWindow.FindName("RemoveItemButton") as Button;
ListBox itemListBox = mainWindow.FindName("ItemListBox") as ListBox;
// Add items to the ListBox
List<string> items = new List<string> { itemToRemove, "Item 2", "Item 3" };
foreach (string item in items)
{
itemListBox.Items.Add(item);
}
// Select an item to remove
itemListBox.SelectedItem = itemToRemove;
// Act
removeButton.RaiseEvent(new RoutedEventArgs(Button.ClickEvent));
// Assert
Assert.IsFalse(itemListBox.Items.Contains(itemToRemove));
}
[Test]
public void LoadDataButton_Clicked_DataServiceIsCalledAndItemsAreAddedToListBox()
{
// Arrange
Button loadDataButton = mainWindow.FindName("LoadDataButton") as Button;
ListBox itemListBox = mainWindow.FindName("ItemListBox") as ListBox;
// Mock data to be returned by the data service
List<string> data = new List<string> { "Item 1", "Item 2", "Item 3" };
dataServiceMock.Setup(service => service.GetData()).Returns(data);
// Act
loadDataButton.RaiseEvent(new RoutedEventArgs(Button.ClickEvent));
// Assert
dataServiceMock.Verify(service => service.GetData(), Times.Once);
Assert.AreEqual(data.Count, itemListBox.Items.Count);
for (int i = 0; i < data.Count; i++)
{
Assert.AreEqual(data[i], itemListBox.Items[i]);
}
}
}
在这个示例中,我们针对一个包含按钮、文本框和列表框等多个控件的WPF主窗口(MainWindow)编写了单元测试。这个示例展示了以下内容:
- 使用
[TestFixture]
标记测试类,使用[SetUp]
在每个测试方法运行之前进行设置。 - 使用
Mock<T>
创建模拟对象来代替实际的依赖项,以确保测试的独立性。 - 使用
FindName
方法查找WPF控件,并进行类型转换以获取特定控件的实例。 - 使用
RaiseEvent
方法模拟按钮的点击事件,以触发相关的事件处理程序。 - 使用
Setup
方法设置模拟对象的行为和返回值,以模拟不同的场景和测试条件。 - 使用
Verify
方法验证模拟对象的方法是否被正确调用。 - 使用
Assert
断言来验证预期结果和实际结果是否一致。
具体而言,示例包括了以下测试方法:
AddItemButton_Clicked_ItemIsAddedToListBox
:测试点击“AddItemButton”按钮后,新项是否被正确添加到列表框中。RemoveItemButton_Clicked_SelectedItemIsRemovedFromListBox
:测试点击“RemoveItemButton”按钮后,选定的项是否从列表框中被正确移除。LoadDataButton_Clicked_DataServiceIsCalledAndItemsAreAddedToListBox
:测试点击“LoadDataButton”按钮后,数据服务的GetData
方法是否被正确调用,并且返回的数据是否正确地添加到列表框中。
这个示例展示了如何使用NUnit和Moq来进行更复杂和全面的WPF控件的单元测试。根据你的实际WPF应用程序的需求和场景,你可以适应和扩展这个示例,并编写适合你项目的测试用例。
24.示例三:ShoppingCartViewModelTests
以下是一个更贴近项目的WPF单元测试代码示例,模拟了一个购物车功能的测试:
using NUnit.Framework;
using Moq;
using System.Collections.ObjectModel;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
[TestFixture]
public class ShoppingCartViewModelTests
{
private ShoppingCartViewModel shoppingCartViewModel;
private Mock<ICartService> cartServiceMock;
private Mock<IDialogService> dialogServiceMock;
private ObservableCollection<Product> mockProducts;
[SetUp]
public void Setup()
{
// 创建购物车服务和对话框服务的模拟对象
cartServiceMock = new Mock<ICartService>();
dialogServiceMock = new Mock<IDialogService>();
// 创建购物车视图模型,并将模拟对象注入
shoppingCartViewModel = new ShoppingCartViewModel(cartServiceMock.Object, dialogServiceMock.Object);
// 模拟一些产品数据
mockProducts = new ObservableCollection<Product>
{
new Product { Id = 1, Name = "Product 1", Price = 10.0 },
new Product { Id = 2, Name = "Product 2", Price = 20.0 },
new Product { Id = 3, Name = "Product 3", Price = 30.0 }
};
}
[Test]
public void AddToCartCommand_Executed_ProductIsAddedToCart()
{
// Arrange
Product selectedProduct = mockProducts.First();
cartServiceMock.Setup(service => service.AddToCart(selectedProduct));
// Act
shoppingCartViewModel.SelectedProduct = selectedProduct;
shoppingCartViewModel.AddToCartCommand.Execute(null);
// Assert
cartServiceMock.Verify(service => service.AddToCart(selectedProduct), Times.Once);
Assert.IsTrue(shoppingCartViewModel.CartItems.Contains(selectedProduct));
}
[Test]
public void RemoveFromCartCommand_Executed_ProductIsRemovedFromCart()
{
// Arrange
Product selectedProduct = mockProducts.First();
shoppingCartViewModel.CartItems.Add(selectedProduct);
cartServiceMock.Setup(service => service.RemoveFromCart(selectedProduct));
// Act
shoppingCartViewModel.SelectedCartItem = selectedProduct;
shoppingCartViewModel.RemoveFromCartCommand.Execute(null);
// Assert
cartServiceMock.Verify(service => service.RemoveFromCart(selectedProduct), Times.Once);
Assert.IsFalse(shoppingCartViewModel.CartItems.Contains(selectedProduct));
}
[Test]
public void CheckoutCommand_Executed_DialogIsShownAndCartIsCleared()
{
// Arrange
shoppingCartViewModel.CartItems.Add(mockProducts.First());
dialogServiceMock.Setup(service => service.ShowConfirmationDialog(It.IsAny<string>(), It.IsAny<string>())).Returns(true);
cartServiceMock.Setup(service => service.Checkout());
// Act
shoppingCartViewModel.CheckoutCommand.Execute(null);
// Assert
dialogServiceMock.Verify(service => service.ShowConfirmationDialog("Checkout", "Are you sure you want to checkout?"), Times.Once);
cartServiceMock.Verify(service => service.Checkout(), Times.Once);
Assert.IsEmpty(shoppingCartViewModel.CartItems);
}
}
在这个示例中,我们针对一个购物车视图模型(ShoppingCartViewModel)编写了单元测试。这个示例展示了以下内容:
- 使用
[TestFixture]
标记测试类,使用[SetUp]
在每个测试方法运行之前进行设置。 - 使用
Mock<T>
创建模拟对象来代替实际的依赖项,以确保测试的独立性。 - 使用
Setup
方法设置模拟对象的行为和返回值,以模拟不同的场景和测试条件。 - 使用
Verify
方法验证模拟对象的方法是否被正确调用。 - 使用
Assert
断言来验证预期结果和实际结果是否一致。
具体而言,示例包括了以下测试方法:
AddToCartCommand_Executed_ProductIsAddedToCart
:测试执行“AddToCartCommand”命令后,选定的产品是否被正确添加到购物车。RemoveFromCartCommand_Executed_ProductIsRemovedFromCart
:测试执行“RemoveFromCartCommand”命令后,选定的产品是否被正确从购物车中移除。CheckoutCommand_Executed_DialogIsShownAndCartIsCleared
:测试执行“CheckoutCommand”命令后,确认对话框是否被正确显示,并且购物车是否被正确清空。
这个示例展示了如何使用NUnit和Moq来进行WPF应用程序中购物车功能的单元测试。你可以根据你的实际项目需求和场景,适应和扩展这个示例,并编写适合你项目的测试用例。
25.示例四:MedicalImageProcessorTests
以下是一个关于医学图像处理的单元测试代码示例:
using NUnit.Framework;
using Moq;
using System.Drawing;
[TestFixture]
public class MedicalImageProcessorTests
{
private MedicalImageProcessor imageProcessor;
private Mock<IMedicalImageLoader> imageLoaderMock;
private Mock<IMedicalImageAnalyzer> imageAnalyzerMock;
private Mock<IMedicalImageVisualizer> imageVisualizerMock;
[SetUp]
public void Setup()
{
// 创建医学图像加载器、图像分析器和图像可视化器的模拟对象
imageLoaderMock = new Mock<IMedicalImageLoader>();
imageAnalyzerMock = new Mock<IMedicalImageAnalyzer>();
imageVisualizerMock = new Mock<IMedicalImageVisualizer>();
// 创建医学图像处理器,并将模拟对象注入
imageProcessor = new MedicalImageProcessor(imageLoaderMock.Object, imageAnalyzerMock.Object, imageVisualizerMock.Object);
}
[Test]
public void LoadAndAnalyzeMedicalImage_ImageIsLoadedAndAnalysisResultsAreReturned()
{
// Arrange
string imagePath = "path/to/medical/image.dcm";
MedicalImage medicalImage = new MedicalImage(imagePath);
MedicalImageAnalysisResult analysisResult = new MedicalImageAnalysisResult();
imageLoaderMock.Setup(loader => loader.LoadImage(imagePath)).Returns(medicalImage);
imageAnalyzerMock.Setup(analyzer => analyzer.AnalyzeImage(medicalImage)).Returns(analysisResult);
// Act
var result = imageProcessor.LoadAndAnalyzeMedicalImage(imagePath);
// Assert
imageLoaderMock.Verify(loader => loader.LoadImage(imagePath), Times.Once);
imageAnalyzerMock.Verify(analyzer => analyzer.AnalyzeImage(medicalImage), Times.Once);
Assert.AreEqual(analysisResult, result);
}
[Test]
public void VisualizeMedicalImage_ImageIsLoadedAndVisualized()
{
// Arrange
string imagePath = "path/to/medical/image.dcm";
MedicalImage medicalImage = new MedicalImage(imagePath);
Image visualizedImage = new Bitmap(800, 600);
imageLoaderMock.Setup(loader => loader.LoadImage(imagePath)).Returns(medicalImage);
imageVisualizerMock.Setup(visualizer => visualizer.VisualizeImage(medicalImage)).Returns(visualizedImage);
// Act
imageProcessor.VisualizeMedicalImage(imagePath);
// Assert
imageLoaderMock.Verify(loader => loader.LoadImage(imagePath), Times.Once);
imageVisualizerMock.Verify(visualizer => visualizer.VisualizeImage(medicalImage), Times.Once);
}
}
在这个示例中,我们针对一个医学图像处理器(MedicalImageProcessor)编写了单元测试。这个示例展示了以下内容:
- 使用
[TestFixture]
标记测试类,使用[SetUp]
在每个测试方法运行之前进行设置。 - 使用
Mock<T>
创建模拟对象来代替实际的依赖项,以确保测试的独立性。 - 使用
Setup
方法设置模拟对象的行为和返回值,以模拟不同的场景和测试条件。 - 使用
Verify
方法验证模拟对象的方法是否被正确调用。 - 使用
Assert
断言来验证预期结果和实际结果是否一致。
具体而言,示例包括了以下测试方法:
LoadAndAnalyzeMedicalImage_ImageIsLoadedAndAnalysisResultsAreReturned
:测试加载医学图像并进行图像分析时,图像是否被正确加载,并且分析结果是否正确返回。VisualizeMedicalImage_ImageIsLoadedAndVisualized
:测试加载医学图像并进行图像可视化时,图像是否被正确加载,并且图像是否被正确可视化。
这个示例展示了如何使用NUnit和Moq来进行医学图像处理软件的单元测试。你可以根据你的实际项目需求和场景,适应和扩展这个示例,并编写适合你项目的测试用例。
以下是医学图像处理器(MedicalImageProcessor)的简单实现示例:
public class MedicalImageProcessor
{
private readonly IMedicalImageLoader imageLoader;
private readonly IMedicalImageAnalyzer imageAnalyzer;
private readonly IMedicalImageVisualizer imageVisualizer;
public MedicalImageProcessor(IMedicalImageLoader imageLoader, IMedicalImageAnalyzer imageAnalyzer, IMedicalImageVisualizer imageVisualizer)
{
this.imageLoader = imageLoader;
this.imageAnalyzer = imageAnalyzer;
this.imageVisualizer = imageVisualizer;
}
public MedicalImageAnalysisResult LoadAndAnalyzeMedicalImage(string imagePath)
{
MedicalImage medicalImage = imageLoader.LoadImage(imagePath);
MedicalImageAnalysisResult analysisResult = imageAnalyzer.AnalyzeImage(medicalImage);
return analysisResult;
}
public void VisualizeMedicalImage(string imagePath)
{
MedicalImage medicalImage = imageLoader.LoadImage(imagePath);
imageVisualizer.VisualizeImage(medicalImage);
}
}
在上述示例中,MedicalImageProcessor
类是医学图像处理器的简单实现。它接收医学图像加载器(IMedicalImageLoader
)、图像分析器(IMedicalImageAnalyzer
)和图像可视化器(IMedicalImageVisualizer
)作为构造函数的参数,并使用它们进行图像处理。
LoadAndAnalyzeMedicalImage
方法接收图像路径作为参数,首先使用图像加载器加载医学图像,然后使用图像分析器对图像进行分析,并返回分析结果。
VisualizeMedicalImage
方法接收图像路径作为参数,同样使用图像加载器加载医学图像,然后使用图像可视化器对图像进行可视化。
通过依赖注入的方式,MedicalImageProcessor
类的实现与具体的图像加载、分析和可视化实现解耦,使得代码更加灵活、可测试和可扩展。
26.自定义测试标签【重要】
在 NUnit 中,你可以使用自定义标签(Custom Attributes)来为测试方法或测试类添加自定义元数据。这些自定义标签可以用于分类、过滤、标记或为测试提供其他相关信息。自定义标签的设计相对简单,需要集合业务情况进行适当处理。部分流行的自定义标签已经被Nunit.Framework接纳吸收,不需要再自定义了。比如作者标签,描述标签等等可以直接使用。
1.标记测试方法的优先级(Priority):
using System;
namespace MyNamespace
{
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
public class PriorityAttribute : Attribute
{
public int Value { get; private set; }
public PriorityAttribute(int value)
{
Value = value;
}
}
}
使用示例:
using NUnit.Framework;
using MyNamespace;
[TestFixture]
public class MyTestClass
{
[Test]
[Priority(1)] // 设置优先级为 1
public void MyTestMethod()
{
// 测试逻辑
}
}
2.标记测试方法的重要性(Importance):
using System;
namespace MyNamespace
{
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
public class ImportanceAttribute : Attribute
{
public ImportanceLevel Level { get; private set; }
public ImportanceAttribute(ImportanceLevel level)
{
Level = level;
}
}
public enum ImportanceLevel
{
Low,
Medium,
High
}
}
使用示例:
using NUnit.Framework;
using MyNamespace;
[TestFixture]
public class MyTestClass
{
[Test]
[Importance(ImportanceLevel.High)] // 设置重要性为 High
public void MyTestMethod()
{
// 测试逻辑
}
}
3.标记测试类的功能模块(Module):
using System;
namespace MyNamespace
{
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
public class ModuleAttribute : Attribute
{
public string Name { get; private set; }
public ModuleAttribute(string name)
{
Name = name;
}
}
}
使用示例:
using NUnit.Framework;
using MyNamespace;
[Module("User Management")] // 标记测试类属于 "User Management" 模块
public class UserManagementTests
{
[Test]
public void CreateUserTest()
{
// 测试逻辑
}
}
4.标记测试方法的作者(Author):
using System;
namespace MyNamespace
{
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
public class AuthorAttribute : Attribute
{
public string Name { get; private set; }
public AuthorAttribute(string name)
{
Name = name;
}
}
}
使用示例:
using NUnit.Framework;
using MyNamespace;
[TestFixture]
public class MyTestClass
{
[Test]
[Author("John Doe")] // 设置作者为 "John Doe"
public void MyTestMethod()
{
// 测试逻辑
}
}
5.标记测试方法的描述(Description):
using System;
namespace MyNamespace
{
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
public class DescriptionAttribute : Attribute
{
public string Text { get; private set; }
public DescriptionAttribute(string text)
{
Text = text;
}
}
}
使用示例:
using NUnit.Framework;
using MyNamespace;
[TestFixture]
public class MyTestClass
{
[Test]
[Description("This test case verifies the login functionality.")] // 设置描述信息
public void LoginTest()
{
// 测试逻辑
}
}
6.标记测试方法的数据源(DataSource):
using System;
namespace MyNamespace
{
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
public class DataSourceAttribute : Attribute
{
public string SourceName { get; private set; }
public DataSourceAttribute(string sourceName)
{
SourceName = sourceName;
}
}
}
使用示例:
using NUnit.Framework;
using MyNamespace;
[TestFixture]
public class MyTestClass
{
[Test]
[DataSource("TestData.csv")] // 设置数据源为 "TestData.csv"
public void DataDrivenTest()
{
// 测试逻辑
}
}
这些示例展示了一些常用的 NUnit 自定义标签,你可以根据实际需求创建更多的自定义标签。通过自定义标签,你可以为测试方法或测试类添加额外的信息和元数据,以便更好地组织和管理测试。
7.示例:自定义标签[FileDataSource]实现读取数据源进行用例测试
要实现从文件中读取测试用例数据作为数据源,并跳过文件的第一行数据,你可以结合使用 NUnit 的 TestCaseSource
特性和自定义标签来实现。以下是具体数据源测试用例
(1)测试数据源文件
a b excepted
1,2,3
0,0,0
-1,-2,-3
-0,-1,-1
-010,-101,-111
以上测试用例仅作演示,不具备测试参考价值。实际测试请另行编写测试用例!
(2)自定义测试标签[FileDataSource(path:)]实现代码
以下是一个示例实现:首先,创建一个自定义的数据源属性,用于从文件中读取测试数据并跳过第一行
using System;
using System.Collections.Generic;
using System.IO;
namespace MyNamespace
{
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
public class FileDataSourceAttribute : Attribute
{
public string FilePath { get; private set; }
public FileDataSourceAttribute(string filePath)
{
FilePath = filePath;
}
public IEnumerable<TestCaseData> GetTestCases()
{
List<TestCaseData> testCases = new List<TestCaseData>();
// 读取测试数据文件
string[] lines = File.ReadAllLines(FilePath);
// 跳过第一行数据(标题行)
for (int i = 1; i < lines.Length; i++)
{
string line = lines[i];
string[] values = line.Split(',');
// 解析测试数据并添加到 TestCaseData 列表
if (
values.Length == 3
&& int.TryParse(values[0], out int input1)
&& int.TryParse(values[1], out int input2)
&& int.TryParse(values[2], out int expectedOutput))
{
TestCaseData testCase = new TestCaseData(input1, input2, expectedOutput);
testCases.Add(testCase);
}
else
{
// 如果测试数据格式不正确,可以选择记录日志或抛出异常
throw new FormatException("Invalid test data format in the file.");
}
}
return testCases;
}
}
}
在上述示例中,我们修改了之前的实现,添加了一个 for
循环来遍历文件的每一行数据,并跳过了第一行(索引为 0)。
然后,你可以在测试方法中使用 FileDataSourceAttribute
和 TestCaseSource
特性,将数据源与测试方法关联起来,就像之前提供的示例中那样。
(3)测试类
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using FileDataSourceAttribute = TestTagBulid.FileDataSourceAttribute;
namespace TestTagBulid
{
[TestFixture]
[Author("ZhangJincheng")]
public class MyTestClass
{
[Test]
[TestCaseSource(typeof(FileDataSourceAttribute), nameof(FileDataSourceAttribute.GetTestCases))]
[FileDataSource(@"C:/MISATDecompile/TestTagBulid/TestData.csv")]
public void DataDrivenTest(int input1, int input2, int expectedOutput)
{
// 执行测试逻辑,并使用断言验证结果是否符合预期
int actualOutput = SomeOperation(input1, input2);
Assert.AreEqual(expectedOutput, actualOutput);
}
private int SomeOperation(int a, int b)
{
// 执行一些操作并返回结果
return a + b;
}
}
}
在上述示例中,我们使用 [FileDataSource]
标签指定了数据源属性为 FileDataSourceAttribute
,并使用 [TestCaseSource]
特性将数据源方法指定为 GetTestCases
。通过这种方式,你可以从文件中读取测试用例数据作为数据源,并在读取数据时跳过第一行。如果不需要则修改for循环即可。