nc_program_manager/.paul/codebase/TESTING.md
dtrentin dabcf989d9 docs: map existing codebase
- STACK.md - Technologies and dependencies (.NET 4.7.2, NuGet, Costura.Fody)
- ARCHITECTURE.md - Layered CLI with ICncMachine factory pattern
- STRUCTURE.md - Directory layout and where to add new code
- CONVENTIONS.md - C# style, naming, result wrapper pattern
- TESTING.md - NUnit 3 + Moq, stubs, ignored FOCAS tests
- INTEGRATIONS.md - FOCAS, LSV2, FTP, RSA licensing
- CONCERNS.md - Blocking async, silent catch, no logging, Windows-only

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-18 22:48:20 +02:00

153 lines
4.8 KiB
Markdown

# Testing Patterns
**Analysis Date:** 2026-05-18
## Test Framework
**Runner:**
- NUnit 3.13.3 — `NcProgramManager.Tests/packages.config`
- NUnit3TestAdapter 4.2.1 — VS/dotnet test runner integration
- Config: `NcProgramManager.Tests/nunit.runsettings`
**Assertion Library:**
- NUnit fluent API: `Assert.That(actual, Is.True)`, `Is.EqualTo()`, `Is.InstanceOf<T>()`
**Mocking:**
- Moq 4.18.4 for interface mocking
- Castle.Core 5.1.1 (Moq dependency)
- Custom delegates for `ref` parameter workaround (Moq limitation with P/Invoke refs)
- Example: `NcProgramManager.Tests/Unit/FanucMachineTests.cs`
**Run Commands:**
```bash
# Standard NUnit runner or VS Test Explorer
dotnet test NcProgramManager.Tests/NcProgramManager.Tests.csproj
# Or via Dockerfile:
docker build -f Dockerfile.test .
```
## Test File Organization
**Location:**
- All tests in `NcProgramManager.Tests/` (separate project)
- Unit tests: `NcProgramManager.Tests/Unit/`
- Integration tests: `NcProgramManager.Tests/Integration/`
- Stubs: `NcProgramManager.Tests/Integration/Stubs/`
**Naming:**
- Unit: `{Subject}Tests.cs``NcProgramValidatorTests.cs`, `FanucMachineTests.cs`
- Integration: `{Subject}IntegrationTests.cs``HeidenhainMachineIntegrationTests.cs`
- Stubs: `{Protocol}ServerStub.cs``FtpServerStub.cs`, `Lsv2ServerStub.cs`
## Test Structure
**Fixture Pattern:**
```csharp
[TestFixture]
public class NcProgramValidatorTests
{
[SetUp]
public void SetUp() { ... }
[Test]
public void MethodName_Condition_ExpectedResult()
{
// arrange
// act
// assert (NUnit fluent)
}
}
```
**Parameterized Tests:**
```csharp
[TestCase(CncManufacturer.Fanuc)]
[TestCase(CncManufacturer.Siemens)]
[TestCase(CncManufacturer.Heidenhain)]
[TestCase(CncManufacturer.Mitsubishi)]
public void Validate_TabCharacter_ReturnsWarning(CncManufacturer m) { ... }
```
Example: `NcProgramManager.Tests/Unit/NcProgramValidatorTests.cs`
**Async Tests:**
- No `async Task` test methods — use `.GetAwaiter().GetResult()` to block
- Example: `NcProgramManager.Tests/Integration/HeidenhainMachineIntegrationTests.cs`
**Test Categories:**
```csharp
[Category("Integration")] // marks integration tests
```
## Mocking
**Interface mocking via Moq:**
```csharp
var mockDialect = new Mock<IFocasDialect>();
mockDialect.Setup(d => d.Connect(...)).Returns(...);
var machine = new FanucMachine(config, mockDialect.Object);
```
Example: `NcProgramManager.Tests/Unit/FanucMachineTests.cs`
**Reflection-based state injection (for bypassing protected fields):**
```csharp
// Set internal connection state without real hardware
typeof(FanucMachine)
.GetField("_state", BindingFlags.NonPublic | BindingFlags.Instance)
.SetValue(machine, ConnectionState.Connected);
```
Example: `NcProgramManager.Tests/Unit/FanucMachineTests.cs`
**What to mock:**
- Protocol interfaces: `IFocasDialect`, `ILsv2Client`, `ISiemensFtpClient`, `IMitsubishiFtpClient`
- Licensing: `IMachineFingerprint`, `ILicenseValidator`
## In-Process Protocol Stubs
**FtpServerStub** (`NcProgramManager.Tests/Integration/Stubs/FtpServerStub.cs`):
- Minimal FTP server for Siemens/Mitsubishi integration testing
- In-memory file store: `Dictionary<string, string> Files`
- Supported commands: USER, PASS, CWD, TYPE, PASV, LIST, NLST, RETR, STOR, DELE
- Usage: Start before test, inject port into machine config
**Lsv2ServerStub** (`NcProgramManager.Tests/Integration/Stubs/Lsv2ServerStub.cs`):
- Minimal LSV2 TCP server for Heidenhain integration testing
- Implements LSV2 framing (STX, SIZE, CMD, data, CRC)
- Thread-per-client architecture
## Ignored Tests
**FOCAS DLL-dependent tests marked `[Ignore]`:**
- All tests requiring actual `Fwlib32.dll` calls are ignored
- Reason: Cannot mock static P/Invoke calls; DLL requires real Fanuc hardware on Windows
- Example: `NcProgramManager.Tests/Unit/FanucMachineTests.cs`
**Pattern for skipping:**
```csharp
[Test]
[Ignore("Requires Fanuc FOCAS DLL and real hardware")]
public void ConnectAsync_ValidConfig_Connects() { ... }
```
## Coverage Approach
**Well-covered:**
- `NcProgramValidator` — universal rules + all 4 manufacturers, 30+ test cases
- `NcProgramManager.Tests/Unit/NcProgramValidatorTests.cs`
- `FanucProgram` parsing — `FanucProgramParsingTests.cs`
- `IsoProgramFormatter``IsoProgramFormatterTests.cs`
- `CncMachineFactory` dispatch — `CncMachineFactoryTests.cs`
- `LicenseValidator` — 6 unit tests via internal ctor injection — `LicenseValidatorTests.cs`
**Partially covered (blocked by hardware):**
- `FanucMachine` connect/read/write — FOCAS tests `[Ignore]`d
- Heidenhain/Siemens/Mitsubishi machines — integration tests use stubs
**Not covered:**
- `MachineFingerprint` (WMI, Windows-only, no mocked tests)
- `LicenseFile` parsing edge cases
- `Program.cs` CLI orchestration (no CLI-level tests)
---
*Testing analysis: 2026-05-18*
*Update when test framework or patterns change*