init
This commit is contained in:
460
GLM5Analysis/PromptTemplates/AddUnitTest.md
Normal file
460
GLM5Analysis/PromptTemplates/AddUnitTest.md
Normal file
@@ -0,0 +1,460 @@
|
||||
# Add Unit Test - DataPRO Prompt Template
|
||||
|
||||
## Context
|
||||
DataPRO uses NUnit for unit testing with test projects in `Common/DTS.Common.Tests/`. Tests follow the Arrange-Act-Assert (AAA) pattern and use `TestCaseSource` for parameterized tests. The testing framework emphasizes clear test naming and comprehensive coverage of edge cases.
|
||||
|
||||
## System Architecture
|
||||
```
|
||||
Common/DTS.Common.Tests/
|
||||
├── ChannelTypeUtilityShould.cs # Example test file
|
||||
├── FilterClassShould.cs
|
||||
├── GroupChannelShould.cs
|
||||
├── LinearizationFormulaShould.cs
|
||||
├── NetworkUtilsShould.cs
|
||||
├── DTS.Common.Tests.csproj
|
||||
└── Properties/
|
||||
└── AssemblyInfo.cs
|
||||
```
|
||||
|
||||
## Step-by-Step Instructions
|
||||
|
||||
### 1. Create Test Class
|
||||
**File:** `Common/DTS.Common.Tests/{ClassName}Should.cs`
|
||||
|
||||
```csharp
|
||||
using NUnit.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace DTS.Common.Tests
|
||||
{
|
||||
[TestFixture]
|
||||
public class {CLASS_NAME}Should
|
||||
{
|
||||
private {CLASS_NAME} _sut; // System Under Test
|
||||
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
_sut = new {CLASS_NAME}();
|
||||
}
|
||||
|
||||
[TearDown]
|
||||
public void TearDown()
|
||||
{
|
||||
// Cleanup if needed
|
||||
_sut = null;
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void MethodName_ShouldReturnExpectedResult_WhenGivenValidInput()
|
||||
{
|
||||
// Arrange
|
||||
var input = "valid input";
|
||||
var expected = "expected output";
|
||||
|
||||
// Act
|
||||
var result = _sut.MethodName(input);
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.EqualTo(expected));
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Basic Test Patterns
|
||||
|
||||
#### Simple Assertion Test
|
||||
```csharp
|
||||
[Test]
|
||||
public void CalculateTotal_ShouldReturnSum_WhenGivenValidNumbers()
|
||||
{
|
||||
// Arrange
|
||||
var calculator = new Calculator();
|
||||
var numbers = new List<int> { 1, 2, 3, 4, 5 };
|
||||
var expected = 15;
|
||||
|
||||
// Act
|
||||
var result = calculator.CalculateTotal(numbers);
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.EqualTo(expected));
|
||||
}
|
||||
```
|
||||
|
||||
#### Null/Empty Input Test
|
||||
```csharp
|
||||
[Test]
|
||||
public void ParseInput_ShouldReturnEmpty_WhenPassedNull()
|
||||
{
|
||||
// Arrange
|
||||
// Act
|
||||
var result = _sut.ParseInput(null);
|
||||
|
||||
// Assert
|
||||
Assert.IsNotNull(result);
|
||||
Assert.That(result, Is.EqualTo(string.Empty));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ParseInput_ShouldReturnEmpty_WhenPassedEmptyString()
|
||||
{
|
||||
// Arrange
|
||||
// Act
|
||||
var result = _sut.ParseInput("");
|
||||
|
||||
// Assert
|
||||
Assert.IsNotNull(result);
|
||||
Assert.That(result, Is.EqualTo(string.Empty));
|
||||
}
|
||||
```
|
||||
|
||||
#### Exception Test
|
||||
```csharp
|
||||
[Test]
|
||||
public void Divide_ShouldThrowDivideByZeroException_WhenDivisorIsZero()
|
||||
{
|
||||
// Arrange
|
||||
var dividend = 10;
|
||||
var divisor = 0;
|
||||
|
||||
// Act & Assert
|
||||
Assert.Throws<DivideByZeroException>(() => _sut.Divide(dividend, divisor));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SetName_ShouldThrowArgumentException_WhenNameIsNull()
|
||||
{
|
||||
// Arrange
|
||||
string name = null;
|
||||
|
||||
// Act & Assert
|
||||
Assert.Throws<ArgumentException>(() => _sut.SetName(name));
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Parameterized Tests
|
||||
|
||||
#### Using TestCase
|
||||
```csharp
|
||||
[TestCase(1, 2, 3)]
|
||||
[TestCase(10, 20, 30)]
|
||||
[TestCase(-5, 5, 0)]
|
||||
[TestCase(0, 0, 0)]
|
||||
public void Add_ShouldReturnCorrectSum_WhenGivenTwoNumbers(int a, int b, int expected)
|
||||
{
|
||||
// Arrange
|
||||
// Act
|
||||
var result = _sut.Add(a, b);
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.EqualTo(expected));
|
||||
}
|
||||
```
|
||||
|
||||
#### Using TestCaseSource
|
||||
```csharp
|
||||
public static IEnumerable<TestCaseData> ChannelTypeTestCases
|
||||
{
|
||||
get
|
||||
{
|
||||
yield return new TestCaseData("AC087155.1", "AC");
|
||||
yield return new TestCaseData("DC123456.2", "DC");
|
||||
yield return new TestCaseData("TM987654.3", "TM");
|
||||
}
|
||||
}
|
||||
|
||||
[TestCaseSource(nameof(ChannelTypeTestCases))]
|
||||
public void ParseChannelType_ShouldReturnCorrectType_WhenGivenValidName(
|
||||
string sensorName, string expectedType)
|
||||
{
|
||||
// Arrange
|
||||
// Act
|
||||
var result = _sut.ParseChannelType(sensorName);
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.EqualTo(expectedType));
|
||||
}
|
||||
```
|
||||
|
||||
#### Generating Test Data from Enum
|
||||
```csharp
|
||||
public static Array GetKnownChannelTypes()
|
||||
{
|
||||
var testValuesFromEnum = new List<string>();
|
||||
var values = Enum.GetValues(typeof(KnownChannelTypes))
|
||||
.Cast<KnownChannelTypes>()
|
||||
.Select(x => x.ToString())
|
||||
.ToArray();
|
||||
|
||||
foreach (var value in values)
|
||||
{
|
||||
testValuesFromEnum.Add($"{value}087155.1");
|
||||
}
|
||||
|
||||
return testValuesFromEnum.ToArray();
|
||||
}
|
||||
|
||||
[TestCaseSource("GetKnownChannelTypes")]
|
||||
public void ParseSensorKnownChannelType_ShouldReturnCorrectTag_WhenPassedSensorNameWithCorrectPrefix(
|
||||
string sensorName)
|
||||
{
|
||||
// Arrange
|
||||
// Act
|
||||
var result = ChannelTypeUtility.ParseSensorKnownChannelType(sensorName);
|
||||
|
||||
// Assert
|
||||
Assert.IsNotNull(result);
|
||||
Assert.IsTrue(Enum.IsDefined(typeof(KnownChannelTypes), result));
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Testing Async Methods
|
||||
```csharp
|
||||
[Test]
|
||||
public async Task LoadDataAsync_ShouldReturnData_WhenDataExists()
|
||||
{
|
||||
// Arrange
|
||||
var expectedCount = 5;
|
||||
|
||||
// Act
|
||||
var result = await _sut.LoadDataAsync();
|
||||
|
||||
// Assert
|
||||
Assert.IsNotNull(result);
|
||||
Assert.That(result.Count, Is.EqualTo(expectedCount));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ProcessAsync_ShouldThrowException_WhenInputInvalid()
|
||||
{
|
||||
// Arrange
|
||||
var invalidInput = "";
|
||||
|
||||
// Act & Assert
|
||||
Assert.ThrowsAsync<ArgumentException>(async () =>
|
||||
await _sut.ProcessAsync(invalidInput));
|
||||
}
|
||||
```
|
||||
|
||||
### 5. Testing Events
|
||||
```csharp
|
||||
[Test]
|
||||
public void ValueChanged_ShouldRaiseEvent_WhenValueIsSet()
|
||||
{
|
||||
// Arrange
|
||||
var eventRaised = false;
|
||||
_sut.ValueChanged += (sender, args) => eventRaised = true;
|
||||
|
||||
// Act
|
||||
_sut.Value = 42;
|
||||
|
||||
// Assert
|
||||
Assert.IsTrue(eventRaised, "ValueChanged event was not raised");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void PropertyChanged_ShouldBeRaised_WhenNameChanges()
|
||||
{
|
||||
// Arrange
|
||||
var eventArgs = new List<string>();
|
||||
_sut.PropertyChanged += (sender, e) => eventArgs.Add(e.PropertyName);
|
||||
|
||||
// Act
|
||||
_sut.Name = "New Name";
|
||||
|
||||
// Assert
|
||||
Assert.Contains("Name", eventArgs);
|
||||
}
|
||||
```
|
||||
|
||||
### 6. Testing Collections
|
||||
```csharp
|
||||
[Test]
|
||||
public void GetItems_ShouldReturnEmptyCollection_WhenNoItemsAdded()
|
||||
{
|
||||
// Arrange
|
||||
// Act
|
||||
var result = _sut.GetItems();
|
||||
|
||||
// Assert
|
||||
Assert.IsNotNull(result);
|
||||
Assert.That(result, Is.Empty);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AddItem_ShouldIncreaseCount_WhenItemIsValid()
|
||||
{
|
||||
// Arrange
|
||||
var item = new Item { Id = 1, Name = "Test" };
|
||||
var initialCount = _sut.ItemCount;
|
||||
|
||||
// Act
|
||||
_sut.AddItem(item);
|
||||
|
||||
// Assert
|
||||
Assert.That(_sut.ItemCount, Is.EqualTo(initialCount + 1));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetItems_ShouldReturnItemsInOrder_WhenItemsAdded()
|
||||
{
|
||||
// Arrange
|
||||
_sut.AddItem(new Item { Id = 3 });
|
||||
_sut.AddItem(new Item { Id = 1 });
|
||||
_sut.AddItem(new Item { Id = 2 });
|
||||
|
||||
// Act
|
||||
var result = _sut.GetItems().ToList();
|
||||
|
||||
// Assert
|
||||
Assert.That(result[0].Id, Is.EqualTo(1));
|
||||
Assert.That(result[1].Id, Is.EqualTo(2));
|
||||
Assert.That(result[2].Id, Is.EqualTo(3));
|
||||
}
|
||||
```
|
||||
|
||||
### 7. Mocking Dependencies (if needed)
|
||||
```csharp
|
||||
using Moq;
|
||||
|
||||
[TestFixture]
|
||||
public class SensorServiceShould
|
||||
{
|
||||
private Mock<ISensorRepository> _mockRepository;
|
||||
private SensorService _sut;
|
||||
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
_mockRepository = new Mock<ISensorRepository>();
|
||||
_sut = new SensorService(_mockRepository.Object);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetSensor_ShouldReturnSensor_WhenSensorExists()
|
||||
{
|
||||
// Arrange
|
||||
var sensorId = 123;
|
||||
var expectedSensor = new Sensor { Id = sensorId, Name = "Test" };
|
||||
_mockRepository.Setup(r => r.Find(sensorId)).Returns(expectedSensor);
|
||||
|
||||
// Act
|
||||
var result = _sut.GetSensor(sensorId);
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.EqualTo(expectedSensor));
|
||||
_mockRepository.Verify(r => r.Find(sensorId), Times.Once);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void SaveSensor_ShouldCallRepository_WhenSensorIsValid()
|
||||
{
|
||||
// Arrange
|
||||
var sensor = new Sensor { Id = 1, Name = "Test" };
|
||||
|
||||
// Act
|
||||
_sut.SaveSensor(sensor);
|
||||
|
||||
// Assert
|
||||
_mockRepository.Verify(r => r.Save(sensor), Times.Once);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Test Naming Convention
|
||||
|
||||
Follow this pattern: `MethodName_Scenario_ExpectedResult`
|
||||
|
||||
Examples:
|
||||
- `ParseSensorName_ShouldReturnNull_WhenPassedNull`
|
||||
- `ParseSensorName_ShouldReturnEmpty_WhenPassedEmptyString`
|
||||
- `ParseSensorName_ShouldReturnCorrectTag_WhenPassedValidName`
|
||||
- `CalculateTotal_ShouldThrowException_WhenListIsNull`
|
||||
|
||||
## Common Assertions
|
||||
|
||||
```csharp
|
||||
// Equality
|
||||
Assert.That(result, Is.EqualTo(expected));
|
||||
Assert.That(result, Is.Not.EqualTo(wrongValue));
|
||||
|
||||
// Null checks
|
||||
Assert.That(result, Is.Null);
|
||||
Assert.That(result, Is.Not.Null);
|
||||
|
||||
// Boolean
|
||||
Assert.That(result, Is.True);
|
||||
Assert.That(result, Is.False);
|
||||
|
||||
// Collections
|
||||
Assert.That(collection, Is.Empty);
|
||||
Assert.That(collection, Is.Not.Empty);
|
||||
Assert.That(collection, Has.Count.EqualTo(5));
|
||||
Assert.That(collection, Does.Contain(item));
|
||||
|
||||
// Exceptions
|
||||
Assert.Throws<ArgumentException>(() => method());
|
||||
Assert.ThrowsAsync<InvalidOperationException>(async () => await method());
|
||||
|
||||
// String
|
||||
Assert.That(result, Does.StartWith("prefix"));
|
||||
Assert.That(result, Does.EndWith("suffix"));
|
||||
Assert.That(result, Does.Contain("substring"));
|
||||
Assert.That(result, Is.Empty);
|
||||
|
||||
// Range
|
||||
Assert.That(value, Is.InRange(1, 10));
|
||||
Assert.That(value, Is.GreaterThan(0));
|
||||
Assert.That(value, Is.LessThan(100));
|
||||
|
||||
// Type checking
|
||||
Assert.That(result, Is.TypeOf<ExpectedType>());
|
||||
Assert.That(result, Is.InstanceOf<IBaseInterface>());
|
||||
```
|
||||
|
||||
## Files to Create Summary
|
||||
|
||||
| File | Action |
|
||||
|------|--------|
|
||||
| `DTS.Common.Tests/{ClassName}Should.cs` | Create |
|
||||
|
||||
## Validation Checklist
|
||||
|
||||
- [ ] Test class has `[TestFixture]` attribute
|
||||
- [ ] Test methods have `[Test]` attribute
|
||||
- [ ] `[SetUp]` used for test initialization
|
||||
- [ ] `[TearDown]` used for cleanup (if needed)
|
||||
- [ ] Test names follow naming convention
|
||||
- [ ] Each test has Arrange-Act-Assert sections
|
||||
- [ ] Edge cases tested (null, empty, boundary values)
|
||||
- [ ] Exception cases tested
|
||||
- [ ] Test is isolated (doesn't depend on other tests)
|
||||
- [ ] No external dependencies (use mocks/stubs)
|
||||
- [ ] Assertions are specific and meaningful
|
||||
|
||||
## Running Tests
|
||||
|
||||
```bash
|
||||
# Run all tests in project
|
||||
dotnet test Common/DTS.Common.Tests/
|
||||
|
||||
# Run specific test class
|
||||
dotnet test --filter "FullyQualifiedName~ChannelTypeUtilityShould"
|
||||
|
||||
# Run specific test method
|
||||
dotnet test --filter "FullyQualifiedName~ChannelTypeUtilityShould.ParseSensorKnownChannelType_ShouldReturnEmpty_WhenPassedNull"
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **One assertion per test** (or logically related assertions)
|
||||
2. **Test behavior, not implementation**
|
||||
3. **Use meaningful test names that describe the scenario**
|
||||
4. **Keep tests independent** - no shared state between tests
|
||||
5. **Test edge cases** - null, empty, max values, boundary conditions
|
||||
6. **Don't test private methods directly** - test through public API
|
||||
7. **Use parameterized tests for similar test cases with different data**
|
||||
8. **Mock external dependencies** - database, file system, network
|
||||
Reference in New Issue
Block a user