- 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>
153 lines
4.8 KiB
Markdown
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*
|