mirror of
https://github.com/PepperDash/Essentials.git
synced 2026-02-14 04:04:58 +00:00
test: initial attempt at tests with Claude Code
This commit is contained in:
@@ -0,0 +1,84 @@
|
||||
using System;
|
||||
using FluentAssertions;
|
||||
using Moq;
|
||||
using PepperDash.Essentials.Core.Abstractions;
|
||||
using Xunit;
|
||||
|
||||
namespace PepperDash.Essentials.Core.Tests.Abstractions
|
||||
{
|
||||
public class DigitalInputTests
|
||||
{
|
||||
[Fact]
|
||||
public void StateChange_WhenDigitalInputChanges_RaisesEvent()
|
||||
{
|
||||
// Arrange
|
||||
var mockDigitalInput = new Mock<IDigitalInput>();
|
||||
var eventRaised = false;
|
||||
bool capturedState = false;
|
||||
|
||||
mockDigitalInput.Setup(d => d.State).Returns(true);
|
||||
|
||||
// Subscribe to the event
|
||||
mockDigitalInput.Object.StateChange += (sender, args) =>
|
||||
{
|
||||
eventRaised = true;
|
||||
capturedState = args.State;
|
||||
};
|
||||
|
||||
// Act - Raise the event
|
||||
mockDigitalInput.Raise(d => d.StateChange += null,
|
||||
mockDigitalInput.Object,
|
||||
new DigitalInputEventArgs(true));
|
||||
|
||||
// Assert
|
||||
eventRaised.Should().BeTrue();
|
||||
capturedState.Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void State_ReturnsCorrectValue()
|
||||
{
|
||||
// Arrange
|
||||
var mockDigitalInput = new Mock<IDigitalInput>();
|
||||
mockDigitalInput.Setup(d => d.State).Returns(true);
|
||||
|
||||
// Act
|
||||
var state = mockDigitalInput.Object.State;
|
||||
|
||||
// Assert
|
||||
state.Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MultipleStateChanges_TrackStateCorrectly()
|
||||
{
|
||||
// Arrange
|
||||
var mockDigitalInput = new Mock<IDigitalInput>();
|
||||
var stateChanges = new System.Collections.Generic.List<bool>();
|
||||
|
||||
mockDigitalInput.Object.StateChange += (sender, args) =>
|
||||
{
|
||||
stateChanges.Add(args.State);
|
||||
};
|
||||
|
||||
// Act - Simulate multiple state changes
|
||||
mockDigitalInput.Raise(d => d.StateChange += null,
|
||||
mockDigitalInput.Object,
|
||||
new DigitalInputEventArgs(true));
|
||||
|
||||
mockDigitalInput.Raise(d => d.StateChange += null,
|
||||
mockDigitalInput.Object,
|
||||
new DigitalInputEventArgs(false));
|
||||
|
||||
mockDigitalInput.Raise(d => d.StateChange += null,
|
||||
mockDigitalInput.Object,
|
||||
new DigitalInputEventArgs(true));
|
||||
|
||||
// Assert
|
||||
stateChanges.Should().HaveCount(3);
|
||||
stateChanges[0].Should().BeTrue();
|
||||
stateChanges[1].Should().BeFalse();
|
||||
stateChanges[2].Should().BeTrue();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,163 @@
|
||||
using System;
|
||||
using FluentAssertions;
|
||||
using Moq;
|
||||
using PepperDash.Essentials.Core.Abstractions;
|
||||
using Xunit;
|
||||
|
||||
namespace PepperDash.Essentials.Core.Tests.Abstractions
|
||||
{
|
||||
public class VersiPortTests
|
||||
{
|
||||
[Fact]
|
||||
public void DigitalIn_ReturnsCorrectValue()
|
||||
{
|
||||
// Arrange
|
||||
var mockVersiPort = new Mock<IVersiPort>();
|
||||
mockVersiPort.Setup(v => v.DigitalIn).Returns(true);
|
||||
|
||||
// Act
|
||||
var digitalIn = mockVersiPort.Object.DigitalIn;
|
||||
|
||||
// Assert
|
||||
digitalIn.Should().BeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SetDigitalOut_SetsCorrectValue()
|
||||
{
|
||||
// Arrange
|
||||
var mockVersiPort = new Mock<IVersiPort>();
|
||||
|
||||
// Act
|
||||
mockVersiPort.Object.SetDigitalOut(true);
|
||||
|
||||
// Assert
|
||||
mockVersiPort.Verify(v => v.SetDigitalOut(true), Times.Once);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AnalogIn_ReturnsCorrectValue()
|
||||
{
|
||||
// Arrange
|
||||
var mockVersiPort = new Mock<IVersiPort>();
|
||||
ushort expectedValue = 32768;
|
||||
mockVersiPort.Setup(v => v.AnalogIn).Returns(expectedValue);
|
||||
|
||||
// Act
|
||||
var analogIn = mockVersiPort.Object.AnalogIn;
|
||||
|
||||
// Assert
|
||||
analogIn.Should().Be(expectedValue);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void VersiportChange_WhenDigitalChanges_RaisesEventWithCorrectType()
|
||||
{
|
||||
// Arrange
|
||||
var mockVersiPort = new Mock<IVersiPort>();
|
||||
var eventRaised = false;
|
||||
VersiPortEventType? capturedEventType = null;
|
||||
object capturedValue = null;
|
||||
|
||||
mockVersiPort.Object.VersiportChange += (sender, args) =>
|
||||
{
|
||||
eventRaised = true;
|
||||
capturedEventType = args.EventType;
|
||||
capturedValue = args.Value;
|
||||
};
|
||||
|
||||
// Act
|
||||
mockVersiPort.Raise(v => v.VersiportChange += null,
|
||||
mockVersiPort.Object,
|
||||
new VersiPortEventArgs
|
||||
{
|
||||
EventType = VersiPortEventType.DigitalInChange,
|
||||
Value = true
|
||||
});
|
||||
|
||||
// Assert
|
||||
eventRaised.Should().BeTrue();
|
||||
capturedEventType.Should().Be(VersiPortEventType.DigitalInChange);
|
||||
capturedValue.Should().Be(true);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void VersiportChange_WhenAnalogChanges_RaisesEventWithCorrectValue()
|
||||
{
|
||||
// Arrange
|
||||
var mockVersiPort = new Mock<IVersiPort>();
|
||||
var eventRaised = false;
|
||||
VersiPortEventType? capturedEventType = null;
|
||||
object capturedValue = null;
|
||||
ushort expectedAnalogValue = 12345;
|
||||
|
||||
mockVersiPort.Object.VersiportChange += (sender, args) =>
|
||||
{
|
||||
eventRaised = true;
|
||||
capturedEventType = args.EventType;
|
||||
capturedValue = args.Value;
|
||||
};
|
||||
|
||||
// Act
|
||||
mockVersiPort.Raise(v => v.VersiportChange += null,
|
||||
mockVersiPort.Object,
|
||||
new VersiPortEventArgs
|
||||
{
|
||||
EventType = VersiPortEventType.AnalogInChange,
|
||||
Value = expectedAnalogValue
|
||||
});
|
||||
|
||||
// Assert
|
||||
eventRaised.Should().BeTrue();
|
||||
capturedEventType.Should().Be(VersiPortEventType.AnalogInChange);
|
||||
capturedValue.Should().Be(expectedAnalogValue);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MultipleVersiportChanges_TracksAllChangesCorrectly()
|
||||
{
|
||||
// Arrange
|
||||
var mockVersiPort = new Mock<IVersiPort>();
|
||||
var changes = new System.Collections.Generic.List<(VersiPortEventType type, object value)>();
|
||||
|
||||
mockVersiPort.Object.VersiportChange += (sender, args) =>
|
||||
{
|
||||
changes.Add((args.EventType, args.Value));
|
||||
};
|
||||
|
||||
// Act - Simulate multiple changes
|
||||
mockVersiPort.Raise(v => v.VersiportChange += null,
|
||||
mockVersiPort.Object,
|
||||
new VersiPortEventArgs
|
||||
{
|
||||
EventType = VersiPortEventType.DigitalInChange,
|
||||
Value = true
|
||||
});
|
||||
|
||||
mockVersiPort.Raise(v => v.VersiportChange += null,
|
||||
mockVersiPort.Object,
|
||||
new VersiPortEventArgs
|
||||
{
|
||||
EventType = VersiPortEventType.AnalogInChange,
|
||||
Value = (ushort)30000
|
||||
});
|
||||
|
||||
mockVersiPort.Raise(v => v.VersiportChange += null,
|
||||
mockVersiPort.Object,
|
||||
new VersiPortEventArgs
|
||||
{
|
||||
EventType = VersiPortEventType.DigitalInChange,
|
||||
Value = false
|
||||
});
|
||||
|
||||
// Assert
|
||||
changes.Should().HaveCount(3);
|
||||
changes[0].type.Should().Be(VersiPortEventType.DigitalInChange);
|
||||
changes[0].value.Should().Be(true);
|
||||
changes[1].type.Should().Be(VersiPortEventType.AnalogInChange);
|
||||
changes[1].value.Should().Be((ushort)30000);
|
||||
changes[2].type.Should().Be(VersiPortEventType.DigitalInChange);
|
||||
changes[2].value.Should().Be(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,193 @@
|
||||
using System.Collections.Generic;
|
||||
using FluentAssertions;
|
||||
using Moq;
|
||||
using PepperDash.Essentials.Core.Abstractions;
|
||||
using PepperDash.Essentials.Core.Devices;
|
||||
using Xunit;
|
||||
|
||||
namespace PepperDash.Essentials.Core.Tests.Devices
|
||||
{
|
||||
public class CrestronProcessorTests
|
||||
{
|
||||
[Fact]
|
||||
public void Constructor_WithValidProcessor_InitializesSwitchedOutputs()
|
||||
{
|
||||
// Arrange
|
||||
var mockProcessor = new Mock<ICrestronControlSystem>();
|
||||
mockProcessor.Setup(p => p.SupportsRelay).Returns(false);
|
||||
|
||||
// Act
|
||||
var processor = new CrestronProcessorTestable("test-processor", mockProcessor.Object);
|
||||
|
||||
// Assert
|
||||
processor.Should().NotBeNull();
|
||||
processor.Key.Should().Be("test-processor");
|
||||
processor.SwitchedOutputs.Should().NotBeNull();
|
||||
processor.SwitchedOutputs.Should().BeEmpty();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetRelays_WhenProcessorSupportsRelays_CreatesRelayDevices()
|
||||
{
|
||||
// Arrange
|
||||
var mockProcessor = new Mock<ICrestronControlSystem>();
|
||||
var mockRelayPort1 = new Mock<IRelayPort>();
|
||||
var mockRelayPort2 = new Mock<IRelayPort>();
|
||||
|
||||
var relayPorts = new Dictionary<uint, IRelayPort>
|
||||
{
|
||||
{ 1, mockRelayPort1.Object },
|
||||
{ 2, mockRelayPort2.Object }
|
||||
};
|
||||
|
||||
mockProcessor.Setup(p => p.SupportsRelay).Returns(true);
|
||||
mockProcessor.Setup(p => p.NumberOfRelayPorts).Returns(2);
|
||||
mockProcessor.Setup(p => p.RelayPorts).Returns(relayPorts);
|
||||
|
||||
// Act
|
||||
var processor = new CrestronProcessorTestable("test-processor", mockProcessor.Object);
|
||||
|
||||
// Assert
|
||||
processor.SwitchedOutputs.Should().HaveCount(2);
|
||||
processor.SwitchedOutputs.Should().ContainKey(1);
|
||||
processor.SwitchedOutputs.Should().ContainKey(2);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetRelays_WhenProcessorDoesNotSupportRelays_DoesNotCreateRelayDevices()
|
||||
{
|
||||
// Arrange
|
||||
var mockProcessor = new Mock<ICrestronControlSystem>();
|
||||
mockProcessor.Setup(p => p.SupportsRelay).Returns(false);
|
||||
|
||||
// Act
|
||||
var processor = new CrestronProcessorTestable("test-processor", mockProcessor.Object);
|
||||
|
||||
// Assert
|
||||
processor.SwitchedOutputs.Should().BeEmpty();
|
||||
mockProcessor.Verify(p => p.NumberOfRelayPorts, Times.Never);
|
||||
mockProcessor.Verify(p => p.RelayPorts, Times.Never);
|
||||
}
|
||||
}
|
||||
|
||||
public class GenericRelayDeviceTests
|
||||
{
|
||||
[Fact]
|
||||
public void OpenRelay_CallsRelayPortOpen()
|
||||
{
|
||||
// Arrange
|
||||
var mockRelayPort = new Mock<IRelayPort>();
|
||||
var device = new GenericRelayDeviceTestable("test-relay", mockRelayPort.Object);
|
||||
|
||||
// Act
|
||||
device.OpenRelay();
|
||||
|
||||
// Assert
|
||||
mockRelayPort.Verify(r => r.Open(), Times.Once);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CloseRelay_CallsRelayPortClose()
|
||||
{
|
||||
// Arrange
|
||||
var mockRelayPort = new Mock<IRelayPort>();
|
||||
var device = new GenericRelayDeviceTestable("test-relay", mockRelayPort.Object);
|
||||
|
||||
// Act
|
||||
device.CloseRelay();
|
||||
|
||||
// Assert
|
||||
mockRelayPort.Verify(r => r.Close(), Times.Once);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PulseRelay_CallsRelayPortPulseWithCorrectDelay()
|
||||
{
|
||||
// Arrange
|
||||
var mockRelayPort = new Mock<IRelayPort>();
|
||||
var device = new GenericRelayDeviceTestable("test-relay", mockRelayPort.Object);
|
||||
const int delayMs = 500;
|
||||
|
||||
// Act
|
||||
device.PulseRelay(delayMs);
|
||||
|
||||
// Assert
|
||||
mockRelayPort.Verify(r => r.Pulse(delayMs), Times.Once);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void On_CallsCloseRelay()
|
||||
{
|
||||
// Arrange
|
||||
var mockRelayPort = new Mock<IRelayPort>();
|
||||
var device = new GenericRelayDeviceTestable("test-relay", mockRelayPort.Object);
|
||||
|
||||
// Act
|
||||
device.On();
|
||||
|
||||
// Assert
|
||||
mockRelayPort.Verify(r => r.Close(), Times.Once);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Off_CallsOpenRelay()
|
||||
{
|
||||
// Arrange
|
||||
var mockRelayPort = new Mock<IRelayPort>();
|
||||
var device = new GenericRelayDeviceTestable("test-relay", mockRelayPort.Object);
|
||||
|
||||
// Act
|
||||
device.Off();
|
||||
|
||||
// Assert
|
||||
mockRelayPort.Verify(r => r.Open(), Times.Once);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PowerToggle_WhenRelayIsOn_CallsOff()
|
||||
{
|
||||
// Arrange
|
||||
var mockRelayPort = new Mock<IRelayPort>();
|
||||
mockRelayPort.Setup(r => r.State).Returns(true); // Relay is ON
|
||||
var device = new GenericRelayDeviceTestable("test-relay", mockRelayPort.Object);
|
||||
|
||||
// Act
|
||||
device.PowerToggle();
|
||||
|
||||
// Assert
|
||||
mockRelayPort.Verify(r => r.Open(), Times.Once);
|
||||
mockRelayPort.Verify(r => r.Close(), Times.Never);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PowerToggle_WhenRelayIsOff_CallsOn()
|
||||
{
|
||||
// Arrange
|
||||
var mockRelayPort = new Mock<IRelayPort>();
|
||||
mockRelayPort.Setup(r => r.State).Returns(false); // Relay is OFF
|
||||
var device = new GenericRelayDeviceTestable("test-relay", mockRelayPort.Object);
|
||||
|
||||
// Act
|
||||
device.PowerToggle();
|
||||
|
||||
// Assert
|
||||
mockRelayPort.Verify(r => r.Close(), Times.Once);
|
||||
mockRelayPort.Verify(r => r.Open(), Times.Never);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsOn_ReturnsRelayPortState()
|
||||
{
|
||||
// Arrange
|
||||
var mockRelayPort = new Mock<IRelayPort>();
|
||||
mockRelayPort.Setup(r => r.State).Returns(true);
|
||||
var device = new GenericRelayDeviceTestable("test-relay", mockRelayPort.Object);
|
||||
|
||||
// Act
|
||||
var isOn = device.IsOn;
|
||||
|
||||
// Assert
|
||||
isOn.Should().BeTrue();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<IsPackable>false</IsPackable>
|
||||
<IsTestProject>true</IsTestProject>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
|
||||
<PackageReference Include="xunit" Version="2.6.5" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.6">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Moq" Version="4.20.70" />
|
||||
<PackageReference Include="FluentAssertions" Version="6.12.0" />
|
||||
<PackageReference Include="coverlet.collector" Version="6.0.0">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\PepperDash.Essentials.Core\PepperDash.Essentials.Core.csproj" />
|
||||
<ProjectReference Include="..\..\src\PepperDash.Core\PepperDash.Core.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
170
tests/README.md
Normal file
170
tests/README.md
Normal file
@@ -0,0 +1,170 @@
|
||||
# PepperDash Essentials Unit Testing Guide
|
||||
|
||||
## Overview
|
||||
|
||||
This guide demonstrates how to write unit tests for PepperDash Essentials despite the Crestron hardware dependencies. The key approach is to use **abstraction layers** and **dependency injection** to isolate Crestron-specific functionality.
|
||||
|
||||
## Architecture Pattern
|
||||
|
||||
### 1. Abstraction Layer
|
||||
We create interfaces that abstract Crestron hardware components:
|
||||
- `ICrestronControlSystem` - Abstracts the control system
|
||||
- `IRelayPort` - Abstracts relay functionality
|
||||
- `IDigitalInput` - Abstracts digital inputs
|
||||
- `IVersiPort` - Abstracts VersiPorts
|
||||
|
||||
### 2. Adapters
|
||||
Adapter classes wrap actual Crestron objects in production:
|
||||
- `CrestronControlSystemAdapter` - Wraps `CrestronControlSystem`
|
||||
- `RelayPortAdapter` - Wraps Crestron `Relay`
|
||||
- `DigitalInputAdapter` - Wraps Crestron `DigitalInput`
|
||||
- `VersiPortAdapter` - Wraps Crestron `Versiport`
|
||||
|
||||
### 3. Testable Classes
|
||||
Create testable versions of classes that accept abstractions:
|
||||
- `CrestronProcessorTestable` - Accepts `ICrestronControlSystem` instead of concrete type
|
||||
- `GenericRelayDeviceTestable` - Accepts `IRelayPort` instead of concrete type
|
||||
|
||||
## Writing Tests
|
||||
|
||||
### Basic Test Example
|
||||
```csharp
|
||||
[Fact]
|
||||
public void OpenRelay_CallsRelayPortOpen()
|
||||
{
|
||||
// Arrange
|
||||
var mockRelayPort = new Mock<IRelayPort>();
|
||||
var device = new GenericRelayDeviceTestable("test-relay", mockRelayPort.Object);
|
||||
|
||||
// Act
|
||||
device.OpenRelay();
|
||||
|
||||
// Assert
|
||||
mockRelayPort.Verify(r => r.Open(), Times.Once);
|
||||
}
|
||||
```
|
||||
|
||||
### Testing Events
|
||||
```csharp
|
||||
[Fact]
|
||||
public void StateChange_WhenDigitalInputChanges_RaisesEvent()
|
||||
{
|
||||
// Arrange
|
||||
var mockDigitalInput = new Mock<IDigitalInput>();
|
||||
var eventRaised = false;
|
||||
|
||||
mockDigitalInput.Object.StateChange += (sender, args) => eventRaised = true;
|
||||
|
||||
// Act
|
||||
mockDigitalInput.Raise(d => d.StateChange += null,
|
||||
mockDigitalInput.Object,
|
||||
new DigitalInputEventArgs(true));
|
||||
|
||||
// Assert
|
||||
eventRaised.Should().BeTrue();
|
||||
}
|
||||
```
|
||||
|
||||
## Running Tests
|
||||
|
||||
### Locally
|
||||
```bash
|
||||
# Run all tests
|
||||
dotnet test
|
||||
|
||||
# Run with coverage
|
||||
dotnet test --collect:"XPlat Code Coverage"
|
||||
|
||||
# Run specific test project
|
||||
dotnet test tests/PepperDash.Essentials.Core.Tests
|
||||
```
|
||||
|
||||
### CI Pipeline
|
||||
Tests run automatically on:
|
||||
- Push to main/develop/net8-updates branches
|
||||
- Pull requests
|
||||
- GitHub Actions workflow generates coverage reports
|
||||
|
||||
## Migration Strategy
|
||||
|
||||
To migrate existing code to be testable:
|
||||
|
||||
1. **Identify Crestron Dependencies**
|
||||
- Search for `using Crestron` statements
|
||||
- Find direct hardware interactions
|
||||
|
||||
2. **Create Abstractions**
|
||||
- Define interfaces for hardware components
|
||||
- Keep interfaces focused and simple
|
||||
|
||||
3. **Implement Adapters**
|
||||
- Wrap Crestron objects with adapters
|
||||
- Map Crestron events to abstraction events
|
||||
|
||||
4. **Refactor Classes**
|
||||
- Accept abstractions via constructor injection
|
||||
- Create factory methods for production use
|
||||
|
||||
5. **Write Tests**
|
||||
- Mock abstractions using Moq
|
||||
- Test business logic independently
|
||||
|
||||
## Best Practices
|
||||
|
||||
### DO:
|
||||
- Keep abstractions simple and focused
|
||||
- Test business logic, not Crestron SDK behavior
|
||||
- Use dependency injection consistently
|
||||
- Mock at the abstraction boundary
|
||||
- Test event handling and state changes
|
||||
|
||||
### DON'T:
|
||||
- Try to mock Crestron types directly
|
||||
- Include hardware-dependent code in tests
|
||||
- Mix business logic with hardware interaction
|
||||
- Create overly complex abstractions
|
||||
|
||||
## Tools Used
|
||||
|
||||
- **xUnit** - Test framework
|
||||
- **Moq** - Mocking framework
|
||||
- **FluentAssertions** - Assertion library
|
||||
- **Coverlet** - Code coverage
|
||||
- **GitHub Actions** - CI/CD
|
||||
|
||||
## Adding New Tests
|
||||
|
||||
1. Create test file in appropriate folder
|
||||
2. Follow naming convention: `[ClassName]Tests.cs`
|
||||
3. Use Arrange-Act-Assert pattern
|
||||
4. Include both positive and negative test cases
|
||||
5. Test edge cases and error conditions
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Common Issues:
|
||||
|
||||
**Tests fail with "Type not found" errors**
|
||||
- Ensure abstractions are properly defined
|
||||
- Check project references
|
||||
|
||||
**Mocked events not firing**
|
||||
- Use `Mock.Raise()` to trigger events
|
||||
- Verify event subscription syntax
|
||||
|
||||
**Coverage not generating**
|
||||
- Run with `--collect:"XPlat Code Coverage"`
|
||||
- Check .gitignore isn't excluding coverage files
|
||||
|
||||
## Example Test Project Structure
|
||||
```
|
||||
tests/
|
||||
├── PepperDash.Essentials.Core.Tests/
|
||||
│ ├── Abstractions/
|
||||
│ │ ├── DigitalInputTests.cs
|
||||
│ │ └── VersiPortTests.cs
|
||||
│ ├── Devices/
|
||||
│ │ └── CrestronProcessorTests.cs
|
||||
│ └── PepperDash.Essentials.Core.Tests.csproj
|
||||
└── README.md
|
||||
```
|
||||
Reference in New Issue
Block a user