编辑:在SO上公开生产代码!希望没有人偷走我的秘密!
我有一个Controller类,用于使用Modbus协议通过TCP与设备通信。我使用NModbus库。
以下是控制器类实现的接口:
public interface CoilReader
{
bool[] Read(ushort startAddress, ushort numberOfCoils);
}
public interface CoilWriter
{
void WriteSingle(ushort address, bool value);
void WriteMultiple(ushort startAddress, bool[] values);
}
public interface HoldingRegisterReader
{
ushort[] Read(ushort startAddress, ushort numberOfRegisters);
}
public interface HoldingRegisterWriter
{
void WriteSingle(ushort address, ushort value);
void WriteMultiple(ushort startAddress, ushort[] values);
}
public interface InputReader
{
bool[] Read(ushort startAddress, ushort numberOfCoils);
}
public interface InputRegisterReader
{
ushort[] Read(ushort startAddress, ushort numberOfRegisters);
}
public interface ConnectionInfo
{
string IP { get; set; }
}
这是控制器类。
using System;
using System.Net.Sockets;
using System.Reflection;
using global::Modbus.Device;
public class Controller
: ConnectionInfo,
HoldingRegisterReader,
InputRegisterReader,
CoilReader,
InputReader,
CoilWriter,
HoldingRegisterWriter
{
static Controller()
{
AppDomain.CurrentDomain.AssemblyResolve += (sender, e) => loadEmbeddedAssembly(e.Name);
}
public virtual string IP
{
get
{
return this.ip;
}
set
{
this.ip = value;
}
}
public virtual ushort[] ReadHoldingRegisters(ushort startAddress, ushort numberOfRegisters)
{
using (var connection = this.createDeviceConnection())
{
return connection.ReadHoldingRegisters(startAddress, numberOfRegisters);
}
}
public virtual ushort[] ReadInputRegisters(ushort startAddress, ushort numberOfRegisters)
{
using (var connection = this.createDeviceConnection())
{
return connection.ReadInputRegisters(startAddress, numberOfRegisters);
}
}
public virtual bool[] ReadCoils(ushort startAddress, ushort numberOfCoils)
{
using (var connection = this.createDeviceConnection())
{
return connection.ReadCoils(startAddress, numberOfCoils);
}
}
public virtual bool[] ReadInputs(ushort startAddress, ushort numberOfInputs)
{
using (var connection = this.createDeviceConnection())
{
return connection.ReadInputs(startAddress, numberOfInputs);
}
}
public virtual void WriteSingleCoil(ushort address, bool value)
{
using (var connection = this.createDeviceConnection())
{
connection.WriteSingleCoil(address, value);
}
}
public virtual void WriteMultipleCoils(ushort startAddress, bool[] values)
{
using (var connection = this.createDeviceConnection())
{
connection.WriteMultipleCoils(startAddress, values);
}
}
public virtual void WriteSingleHoldingRegister(ushort address, ushort value)
{
using (var connection = this.createDeviceConnection())
{
connection.WriteSingleRegister(address, value);
}
}
public virtual void WriteMultipleHoldingRegisters(ushort startAddress, ushort[] values)
{
using (var connection = this.createDeviceConnection())
{
connection.WriteMultipleRegisters(startAddress, values);
}
}
string ConnectionInfo.IP
{
get
{
return this.IP;
}
set
{
this.IP = value;
}
}
ushort[] HoldingRegisterReader.Read(ushort startAddress, ushort numberOfRegisters)
{
return this.ReadHoldingRegisters(startAddress, numberOfRegisters);
}
ushort[] InputRegisterReader.Read(ushort startAddress, ushort numberOfRegisters)
{
return this.ReadInputRegisters(startAddress, numberOfRegisters);
}
bool[] CoilReader.Read(ushort startAddress, ushort numberOfCoils)
{
return this.ReadCoils(startAddress, numberOfCoils);
}
bool[] InputReader.Read(ushort startAddress, ushort numberOfInputs)
{
return this.ReadInputs(startAddress, numberOfInputs);
}
void CoilWriter.WriteSingle(ushort address, bool value)
{
this.WriteSingleCoil(address, value);
}
void CoilWriter.WriteMultiple(ushort startAddress, bool[] values)
{
this.WriteMultipleCoils(startAddress, values);
}
void HoldingRegisterWriter.WriteSingle(ushort address, ushort value)
{
this.WriteSingleHoldingRegister(address, value);
}
void HoldingRegisterWriter.WriteMultiple(ushort startAddress, ushort[] values)
{
this.WriteMultipleHoldingRegisters(startAddress, values);
}
private ModbusIpMaster createDeviceConnection()
{
const int port = 502;
var client = new TcpClient();
client.BeginConnect(this.ip, port, null, null).AsyncWaitHandle.WaitOne(TimeSpan.FromSeconds(2));
if (!client.Connected)
{
throw new Exception("Cannot connect to " + this.ip + ":" + port);
}
return ModbusIpMaster.CreateIp(client);
}
private static Assembly loadEmbeddedAssembly(string name)
{
if (name.EndsWith("Retargetable=Yes"))
{
return Assembly.Load(new AssemblyName(name));
}
var container = Assembly.GetExecutingAssembly();
var path = new AssemblyName(name).Name + ".dll";
using (var stream = container.GetManifestResourceStream(path))
{
if (stream == null)
{
return null;
}
var bytes = new byte[stream.Length];
stream.Read(bytes, 0, bytes.Length);
return Assembly.Load(bytes);
}
}
private string ip;
}
我没有从与包含此类及其接口的库相同的解决方案中的测试项目中创建的测试中获得以下错误。但是,在使用不同解决方案的项目中,使用库时,我得到以下结果:
------ Test started: Assembly: CareControls.IvisHmi.Tests.dll ------
Unknown .NET Framework Version: v4.5.1
Test 'CareControls.IvisHmi.Tests.Presenters.ModbusTcpTogglePresenterTests.FactMethodName' failed: FakeItEasy.Core.FakeCreationException :
Failed to create fake of type "CareControls.Modbus.Tcp.Controller".
Below is a list of reasons for failure per attempted constructor:
No constructor arguments failed:
No default constructor was found on the type CareControls.Modbus.Tcp.Controller.
If either the type or constructor is internal, try adding the following attribute to the assembly:
[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")]
at FakeItEasy.Core.DefaultExceptionThrower.ThrowFailedToGenerateProxyWithResolvedConstructors(Type typeOfFake, String reasonForFailureOfUnspecifiedConstructor, IEnumerable`1 resolvedConstructors)
at FakeItEasy.Creation.FakeObjectCreator.TryCreateFakeWithDummyArgumentsForConstructor(Type typeOfFake, FakeOptions fakeOptions, IDummyValueCreationSession session, String failReasonForDefaultConstructor, Boolean throwOnFailure)
at FakeItEasy.Creation.FakeObjectCreator.CreateFake(Type typeOfFake, FakeOptions fakeOptions, IDummyValueCreationSession session, Boolean throwOnFailure)
at FakeItEasy.Creation.DefaultFakeAndDummyManager.CreateFake(Type typeOfFake, FakeOptions options)
at FakeItEasy.Creation.DefaultFakeCreatorFacade.CreateFake[T](Action`1 options)
at FakeItEasy.A.Fake[T]()
Presenters\ModbusTcpTogglePresenterTests.cs(22,0): at CareControls.IvisHmi.Tests.Presenters.ModbusTcpTogglePresenterTests.FactMethodName()
0 passed, 1 failed, 0 skipped, took 0.93 seconds (xUnit.net 1.9.2 build 1705).
这是测试:
namespace CareControls.IvisHmi.Tests.Presenters
{
using CareControls.IvisHmi.Framework;
using CareControls.IvisHmi.Presenters;
using CareControls.IvisHmi.UI;
using CareControls.Modbus.Tcp;
using FakeItEasy;
using Ploeh.AutoFixture;
using Xunit;
public class ModbusTcpTogglePresenterTests
{
[Fact]
public void FactMethodName()
{
A.Fake<Controller>();
}
}
}
为什么FakeItEasy不认为类上有默认构造函数?
对于大量帖子感到抱歉,但要求我提供代码。
修改:如果我在new Controller()
行之前添加A.Fake<Controller>()
,则测试通过:
namespace CareControls.IvisHmi.Tests.Presenters
{
using CareControls.IvisHmi.Framework;
using CareControls.IvisHmi.Presenters;
using CareControls.IvisHmi.UI;
using CareControls.Modbus.Tcp;
using FakeItEasy;
using Ploeh.AutoFixture;
using Xunit;
public class ModbusTcpTogglePresenterTests
{
[Fact]
public void FactMethodName()
{
new Controller();
A.Fake<Controller>();
}
}
}
答案 0 :(得分:2)
根据这个:
Faking/mocking an interface gives "no default constructor" error, how can that be?
有一个错误,可以给出错误的异常消息。他们说在那种情况下,仍然应该有一些事情导致异常被抛出。
我建议的事情:
Activator.CreateInstance
?答案 1 :(得分:1)
静态构造函数与公共构造函数不同。在定义静态构造函数时,您确定编译器会生成默认的公共构造函数吗?我不会想到你会两个都得到。如果你use ILDASM
to reverse engineer the assembly会出现吗?
静态构造函数可能被称为“懒惰”,因此当FakeItEasy尝试通过反映类型来创建伪造时,静态构造函数尚未被调用。这可以解释为什么在创建伪造之前实例化类型时它会起作用。
答案 2 :(得分:1)
总结Sam Pearson使用FakeItEasy 1.18.0以及MSDN文档收集的信息时,问题来自于无法加载Modbus
程序集时抛出的异常。这很有趣,因为Controller
的静态构造函数包含处理此失败的代码,因此它不能被执行。静态构造函数称为
...在创建第一个实例或引用任何静态成员之前自动初始化类。1
但是,FakeItEasy没有故意做这些事情。 似乎DynamicProxy也没有。
我的猜测是访问该类以便伪造它会触发Modbus程序集加载,而不会触发静态构造函数。
但是,FakeItEasy的装配扫描可能会触发装配负载
这可以通过添加一个伪造其他内容的测试来检查,例如ICollection
,然后运行它
如果错误仍然发生,那么它就是正在进行的目录扫描。在这种情况下,您可以使用FakeItEasy 1.18.0中添加的新Bootstrapper来禁用目录扫描。
我们还没有官方文档,因为有一个ongoing discussion about changing the scanning mechanism a little,但我确实发布了how to disable the scanning of on-disk assemblies。 如果 FakeItEasy的扫描触发了加载,并且禁用扫描会延迟程序集加载,直到调用静态构造函数之后,这可能是最简单的事情。
其他可能的解决方法:
Controller
是一种解决方法(如前所述)Controller
实现的许多接口中的一个(或全部)而不是伪造Controller
(我的个人偏好是假接口,而不是类,但有时需要)答案 3 :(得分:0)
我通过将createDeviceConnection()
方法转换为NModbusDeviceConnection
一次性类来解决问题。在NModbusDeviceConnection
的静态构造函数中,订阅了AssemblyResolve
事件。现在我可以创建Controller
的假货而不会触发Modbus
装配加载。
有关详细信息,请参阅NModbusDeviceConnection
类:
// ---------------------------------------------------------------------------------------------------------------------
// <copyright file="NModbusDeviceConnection.cs" company="Care Controls">
// Copyright (c) Care Controls Inc. All rights reserved.
// </copyright>
// ---------------------------------------------------------------------------------------------------------------------
namespace CareControls.Modbus.Tcp.Internal
{
using System;
using System.Net.Sockets;
using System.Reflection;
using global::Modbus.Device;
internal sealed class NModbusDeviceConnection : IDisposable
{
static NModbusDeviceConnection()
{
AppDomain.CurrentDomain.AssemblyResolve += (sender, e) => loadEmbeddedAssembly(e.Name);
}
public NModbusDeviceConnection(string ip)
{
const int port = 502;
var client = new TcpClient();
client.BeginConnect(ip, port, null, null).AsyncWaitHandle.WaitOne(TimeSpan.FromSeconds(2));
if (!client.Connected)
{
throw new Exception("Cannot connect to " + ip + ":" + port);
}
this.connection = ModbusIpMaster.CreateIp(client);
}
public ushort[] ReadHoldingRegisters(ushort startAddress, ushort numberOfRegisters)
{
return this.connection.ReadHoldingRegisters(startAddress, numberOfRegisters);
}
public ushort[] ReadInputRegisters(ushort startAddress, ushort numberOfRegisters)
{
return this.connection.ReadInputRegisters(startAddress, numberOfRegisters);
}
public bool[] ReadCoils(ushort startAddress, ushort numberOfCoils)
{
return this.connection.ReadCoils(startAddress, numberOfCoils);
}
public bool[] ReadInputs(ushort startAddress, ushort numberOfInputs)
{
return this.connection.ReadInputs(startAddress, numberOfInputs);
}
public void WriteSingleCoil(ushort address, bool value)
{
this.connection.WriteSingleCoil(address, value);
}
public void WriteMultipleCoils(ushort startAddress, bool[] values)
{
this.connection.WriteMultipleCoils(startAddress, values);
}
public void WriteSingleHoldingRegister(ushort address, ushort value)
{
this.connection.WriteSingleRegister(address, value);
}
public void WriteMultipleHoldingRegisters(ushort address, ushort[] values)
{
this.connection.WriteMultipleRegisters(address, values);
}
public void Dispose()
{
if (this.connection != null)
{
this.connection.Dispose();
}
}
private static Assembly loadEmbeddedAssembly(string name)
{
if (name.EndsWith("Retargetable=Yes"))
{
return Assembly.Load(new AssemblyName(name));
}
var container = Assembly.GetExecutingAssembly();
var path = new AssemblyName(name).Name + ".dll";
using (var stream = container.GetManifestResourceStream(path))
{
if (stream == null)
{
return null;
}
var bytes = new byte[stream.Length];
stream.Read(bytes, 0, bytes.Length);
return Assembly.Load(bytes);
}
}
private readonly ModbusIpMaster connection;
}
}
这是修改后的Controller
类:
// ---------------------------------------------------------------------------------------------------------------------
// <copyright file="Controller.cs" company="Care Controls">
// Copyright (c) Care Controls Inc. All rights reserved.
// </copyright>
// ---------------------------------------------------------------------------------------------------------------------
namespace CareControls.Modbus.Tcp
{
using System;
using CareControls.Modbus.Tcp.Internal;
public class Controller
: ConnectionInfo,
HoldingRegisterReader,
InputRegisterReader,
CoilReader,
InputReader,
CoilWriter,
HoldingRegisterWriter
{
public Controller()
{
this.newDeviceConnection = () => new NModbusDeviceConnection(this.IP);
}
public virtual string IP { get; set; }
public virtual ushort[] ReadHoldingRegisters(ushort startAddress, ushort numberOfRegisters)
{
using (var connection = this.newDeviceConnection())
{
return connection.ReadHoldingRegisters(startAddress, numberOfRegisters);
}
}
public virtual ushort[] ReadInputRegisters(ushort startAddress, ushort numberOfRegisters)
{
using (var connection = this.newDeviceConnection())
{
return connection.ReadInputRegisters(startAddress, numberOfRegisters);
}
}
public virtual bool[] ReadCoils(ushort startAddress, ushort numberOfCoils)
{
using (var connection = this.newDeviceConnection())
{
return connection.ReadCoils(startAddress, numberOfCoils);
}
}
public virtual bool[] ReadInputs(ushort startAddress, ushort numberOfInputs)
{
using (var connection = this.newDeviceConnection())
{
return connection.ReadInputs(startAddress, numberOfInputs);
}
}
public virtual void WriteSingleCoil(ushort address, bool value)
{
using (var connection = this.newDeviceConnection())
{
connection.WriteSingleCoil(address, value);
}
}
public virtual void WriteMultipleCoils(ushort startAddress, bool[] values)
{
using (var connection = this.newDeviceConnection())
{
connection.WriteMultipleCoils(startAddress, values);
}
}
public virtual void WriteSingleHoldingRegister(ushort address, ushort value)
{
using (var connection = this.newDeviceConnection())
{
connection.WriteSingleHoldingRegister(address, value);
}
}
public virtual void WriteMultipleHoldingRegisters(ushort startAddress, ushort[] values)
{
using (var connection = this.newDeviceConnection())
{
connection.WriteMultipleHoldingRegisters(startAddress, values);
}
}
string ConnectionInfo.IP
{
get
{
return this.IP;
}
set
{
this.IP = value;
}
}
ushort[] HoldingRegisterReader.Read(ushort startAddress, ushort numberOfRegisters)
{
return this.ReadHoldingRegisters(startAddress, numberOfRegisters);
}
ushort[] InputRegisterReader.Read(ushort startAddress, ushort numberOfRegisters)
{
return this.ReadInputRegisters(startAddress, numberOfRegisters);
}
bool[] CoilReader.Read(ushort startAddress, ushort numberOfCoils)
{
return this.ReadCoils(startAddress, numberOfCoils);
}
bool[] InputReader.Read(ushort startAddress, ushort numberOfInputs)
{
return this.ReadInputs(startAddress, numberOfInputs);
}
void CoilWriter.WriteSingle(ushort address, bool value)
{
this.WriteSingleCoil(address, value);
}
void CoilWriter.WriteMultiple(ushort startAddress, bool[] values)
{
this.WriteMultipleCoils(startAddress, values);
}
void HoldingRegisterWriter.WriteSingle(ushort address, ushort value)
{
this.WriteSingleHoldingRegister(address, value);
}
void HoldingRegisterWriter.WriteMultiple(ushort startAddress, ushort[] values)
{
this.WriteMultipleHoldingRegisters(startAddress, values);
}
private readonly Func<NModbusDeviceConnection> newDeviceConnection;
}
}