7.6 KiB
OPC UA Client test fixture
Coverage map + gap inventory for the OPC UA Client (gateway / aggregation) driver.
TL;DR: Wire-level coverage now exists via
opc-plc — Microsoft
Industrial IoT's OPC UA PLC simulator running in Docker (task #215). Real
Secure Channel, real Session, real MonitoredItem exchange against an
independent server implementation. Unit tests still carry the exhaustive
capability matrix (cert auth / security policies / reconnect / failover /
attribute mapping). Gaps remaining: upstream-server-specific quirks
(historian aggregates, typed ConditionType events, SDK-publish-queue edge
behavior under load) — opc-plc uses the same OPCFoundation stack internally
so fully-independent-stack coverage needs open62541/open62541 as a second
image (follow-up).
What the fixture is
Integration layer (task #215):
tests/ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient.IntegrationTests/ stands up
mcr.microsoft.com/iotedge/opc-plc:2.14.10 via Docker/docker-compose.yml
on opc.tcp://localhost:50000. OpcPlcFixture probes the port at
collection init + skips tests with a clear message when the container's
not running (matches the Modbus/pymodbus + S7/python-snap7 skip pattern).
Docker is the launcher — no PowerShell wrapper needed because opc-plc
ships pre-containerized. Compose-file flags: --ut (unsecured transport
advertised), --aa (auto-accept client certs — opc-plc's cert trust store
resets on each spin-up), --alm (alarm simulation for IAlarmSource
follow-up coverage), --pn=50000 (port).
Unit layer:
tests/ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient.Tests/ is still the primary
coverage. Tests inject fakes through the driver's construction path; the
OPCFoundation.NetStandard Session surface is wrapped behind an interface
the tests mock.
What it actually covers
Integration (opc-plc Docker, task #215)
OpcUaClientSmokeTests.Client_connects_and_reads_StepUp_node_through_real_OPC_UA_stack— full Secure Channel + Session +ns=3;s=StepUpRead round-tripOpcUaClientSmokeTests.Client_reads_batch_of_varied_types_from_live_simulator— batch Read of UInt32 / Int32 / Boolean; assertsbool-specific Variant decoding to catch a common attribute-mapping regressionOpcUaClientSmokeTests.Client_subscribe_receives_StepUp_data_changes_from_live_server— realMonitoredItemsubscription againstns=3;s=FastUInt1(ticks every 100 ms); assertsOnDataChangefires within 3 s of subscribe
Wire-level surfaces verified: IDriver + IReadable + ISubscribable +
IHostConnectivityProbe (via the Secure Channel exchange).
Unit
The surface is broad because OpcUaClientDriver is the richest-capability
driver in the fleet (it's a gateway for another OPC UA server, so it
mirrors the full capability matrix):
OpcUaClientDriverScaffoldTests—IDriverlifecycleOpcUaClientReadWriteTests— read + write lifecycleOpcUaClientSubscribeAndProbeTests— monitored-item subscription + probe state transitionsOpcUaClientDiscoveryTests—GetEndpoints+ endpoint selectionOpcUaClientAttributeMappingTests— OPC UA node attribute → driver value mappingOpcUaClientSecurityPolicyTests—SignAndEncrypt/Sign/Nonepolicy negotiation contractOpcUaClientCertAuthTests— cert store paths, revocation-list configOpcUaClientReconnectTests— SDK reconnect hook +TransferSubscriptionsacross the disconnect boundaryOpcUaClientFailoverTests— primary → secondary session fallback per driver configOpcUaClientAlarmTests— A&E severity bucket (1–1000 → Low / Medium / High / Critical), subscribe / unsubscribe / ack contractOpcUaClientHistoryTests— historical data read + interpolation contract
Capability surfaces whose contract is verified: IDriver, ITagDiscovery,
IReadable, IWritable, ISubscribable, IHostConnectivityProbe,
IAlarmSource, IHistoryProvider.
What it does NOT cover
1. Real stack exchange
No UA Secure Channel is ever opened. Every test mocks Session.ReadAsync,
Session.CreateSubscription, Session.AddItem, etc. — the SDK itself is
trusted. Certificate validation, signing, nonce handling, chunk assembly,
keep-alive cadence — all SDK-internal and untested here.
2. Subscription transfer across reconnect
Contract test: "after a simulated reconnect, TransferSubscriptions is
called with the right handles." Real behavior: SDK re-publishes against the
new channel and some events can be lost depending on publish-queue state.
The lossy window is not characterized.
3. Large-scale subscription stress
100+ monitored items with heterogeneous publish intervals under a single session — the shape that breaks publish-queue-size tuning in the wild — is not exercised.
4. Real historian mappings
IHistoryProvider.ReadRawAsync + ReadProcessedAsync +
ReadAtTimeAsync + ReadEventsAsync are contract-mocked. Against a real
historian (AVEVA Historian, Prosys historian, Kepware LocalHistorian) each
has specific interpolation + bad-quality-handling quirks the contract test
doesn't see.
5. Real A&E events
Alarm subscription is mocked via filtered monitored items; the actual
EventFilter select-clause behavior against a server that exposes typed
ConditionType events (non-base BaseEventType) is not verified.
6. Authentication variants
- Anonymous, UserName/Password, X509 cert tokens — each is contract-tested but not exchanged against a server that actually enforces each.
- LDAP-backed
UserName(matching this repo's server-sideLdapUserAuthenticator) requires a live LDAP round-trip; not tested.
When to trust OpcUaClient tests, when to reach for a server
| Question | Unit tests | Real upstream server |
|---|---|---|
| "Does severity 750 bucket as High?" | yes | yes |
"Does the driver call TransferSubscriptions after reconnect?" |
yes | yes |
| "Does a real OPC UA read/write round-trip work?" | no | yes (required) |
| "Does event-filter-based alarm subscription return ConditionType events?" | no | yes (required) |
| "Does history read from AVEVA Historian return correct aggregates?" | no | yes (required) |
| "Does the SDK's publish queue lose notifications under load?" | no | yes (stress) |
Follow-up candidates
The easiest win here is to wire the client driver tests against this
repo's own server. The integration test project
tests/ZB.MOM.WW.OtOpcUa.Server.Tests/OpcUaServerIntegrationTests.cs
already stands up a real OPC UA server on a non-default port with a seeded
FakeDriver. An OpcUaClientLiveLoopbackTests that connects the client
driver to that server would give:
- Real Secure Channel negotiation
- Real Session / Subscription / MonitoredItem exchange
- Real read/write round-trip
- Real certificate validation (the integration test already sets up PKI)
It wouldn't cover upstream-server-specific quirks (AVEVA Historian, Kepware, Prosys), but it would cover 80% of the SDK surface the driver sits on top of.
Beyond that:
- Prosys OPC UA Simulation Server — free, Windows-available, scriptable.
- UaExpert Server-Side Simulator — Unified Automation's sample server; good coverage of typed ConditionType events.
- Dedicated historian integration lab — only path for historian-specific coverage.
Key fixture / config files
tests/ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient.Tests/— unit tests with mockedSessionsrc/ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient/OpcUaClientDriver.cs— ctor + session-factory seam tests mock throughtests/ZB.MOM.WW.OtOpcUa.Server.Tests/OpcUaServerIntegrationTests.cs— the server-side integration harness a future loopback client test could piggyback on