为什么 NSubstitute returns 指定倍数时仅最后一个值
Why NSubstitute returns only last value when multiples are specified
我试图在我的 DAO class 中模拟 IDbConnection,但我收到以下错误:
字符串长度均为 5。字符串在索引 0 处不同。
预期:“11111”
但是是:“22222”
这是我的代码:
类 测试中
internal class ImportAcquisitionDataDAO : IImportAcquisitionDataDAO
{
private static Logger log = LogManager.GetCurrentClassLogger();
private readonly IDbConnection connection;
internal ImportAcquisitionDataDAO(IDbConnection connection)
{
this.connection = connection;
}
internal List<DefinitionEntry> GetDefinitions()
{
log.Debug("Getting definitions from database.");
var definitions = new List<DefinitionEntry>();
using (connection)
{
connection.Open();
log.Trace("Database connection opened");
IDbCommand command = connection.CreateCommand();
command.CommandText = @"SELECT *
FROM MAPPING";
IDataReader reader = command.ExecuteReader();
log.Trace("Command executed:\n{0}", command.CommandText);
definitions = GetMeterEntries(reader);
}
log.Debug("Obtained {0} definitions.", definitions.Count);
return definitions;
}
private List<DefinitionEntry> GetMeterEntries(IDataReader reader)
{
log.Trace("Parsing definitions from response");
var result = new List<DefinitionEntry>();
while (reader.Read())
{
var definition = new DefinitionEntry(
reader.GetString(0),
reader.GetString(1),
reader.GetString(2),
reader.IsDBNull(3) ? null : reader.GetString(3),
reader.IsDBNull(4) ? null : reader.GetString(4),
reader.IsDBNull(5) ? null : reader.GetString(5),
reader.IsDBNull(6) ? null : reader.GetString(6)
);
log.Trace(definition.ToString());
result.Add(definition);
}
return result;
}
}
public class DefinitionEntry
{
public string MeterSN { get; private set; }
public string MATNR { get; private set; }
public string IpAddress { get; private set; }
public string SIM { get; private set; }
public string ModulID { get; private set; }
public string SIMUser { get; private set; }
public string SIMPassword { get; private set; }
public DefinitionEntry(string meterSN, string matnr, string ipAddress, string sim, string modulId, string simUser, string simPassword)
{
MeterSN = meterSN;
MATNR = matnr;
IpAddress = ipAddress;
SIM = sim;
ModulID = modulId;
SIMUser = simUser;
SIMPassword = simPassword;
}
}
测试class
[TestFixture]
public class ImportAcquisitionDataDAOTests
{
private IDbConnection mockConnection;
private IDbCommand mockCommand;
private IDataReader mockReader;
private ImportAcquisitionDataDAO dao;
[SetUp]
public void SetUp()
{
mockConnection = Substitute.For<IDbConnection>();
mockCommand = Substitute.For<IDbCommand>();
mockReader = Substitute.For<IDataReader>();
dao = new ImportAcquisitionDataDAO(mockConnection);
mockConnection.CreateCommand().Returns(mockCommand);
mockCommand.ExecuteReader().Returns(mockReader);
}
[Test]
public void TestGetDefinitions()
{
// mock
var databaseDefinitionFirst = new DefinitionEntry("11111", "AS3000-5/100-400-P", "10.42.42.26", "SIM-001", "12345lkj", "alibaba", "abrakadabra");
var databaseDefinitionSecond = new DefinitionEntry("22222", "AS3000-5/100-400-Q", "10.42.42.158", null, null, null, null);
mockReader.Read().Returns(true, true, false);
mockReader.GetString(Arg.Is<int>(0)).Returns(databaseDefinitionFirst.MeterSN, databaseDefinitionSecond.MeterSN);
mockReader.GetString(Arg.Is<int>(1)).Returns(databaseDefinitionFirst.MATNR, databaseDefinitionSecond.MATNR);
mockReader.GetString(Arg.Is<int>(2)).Returns(databaseDefinitionFirst.IpAddress, databaseDefinitionSecond.IpAddress);
mockReader.IsDBNull(Arg.Is<int>(3)).Returns(false, true);
mockReader.GetString(Arg.Is<int>(3)).Returns(databaseDefinitionFirst.SIM);
mockReader.IsDBNull(Arg.Is<int>(4)).Returns(false, true);
mockReader.GetString(Arg.Is<int>(4)).Returns(databaseDefinitionFirst.ModulID);
mockReader.IsDBNull(Arg.Is<int>(5)).Returns(false, true);
mockReader.GetString(Arg.Is<int>(5)).Returns(databaseDefinitionFirst.SIMUser);
mockReader.IsDBNull(Arg.Is<int>(6)).Returns(false, true);
mockReader.GetString(Arg.Is<int>(6)).Returns(databaseDefinitionFirst.SIMPassword);
// use
List<DefinitionEntry> tested = dao.GetDefinitions();
// verify
Assert.AreEqual(2, tested.Count);
AssertDefinitionEntry(databaseDefinitionFirst, tested.First());
AssertDefinitionEntry(databaseDefinitionSecond, tested.Last());
}
private void AssertDefinitionEntry(DefinitionEntry expected, DefinitionEntry tested)
{
Assert.AreEqual(expected.MeterSN, tested.MeterSN);
Assert.AreEqual(expected.MATNR, tested.MATNR);
Assert.AreEqual(expected.IpAddress, tested.IpAddress);
Assert.AreEqual(expected.SIM, tested.SIM);
Assert.AreEqual(expected.ModulID, tested.ModulID);
Assert.AreEqual(expected.SIMUser, tested.SIMUser);
Assert.AreEqual(expected.SIMPassword, tested.SIMPassword);
}
}
这是执行第一个 Assert 之前的调试屏幕:
enter image description here
除 MeterSN 字段外,我的所有设置均符合我的预期,这两个条目都相同。但我无法找出原因
这是一个相当微妙的问题。
简答:
将 mockReader.GetString(Arg.Is<int>(0)).Returns(databaseDefinitionFirst.MeterSN, databaseDefinitionSecond.MeterSN);
移动到最后一个被存根的调用(就在 mockReader.GetString(Arg.Is<int>(6))
之下)。
更长的答案:
当我们说 mockReader.GetString(Arg.Is<int>(5))
时,Arg.Is
部分 returns 为零,这意味着 mockReader.GetString(0)
在测试设置中被多次调用。所以“11111”确实首先按照配置返回,但是当 GetDefinitions
被调用时,该值已经被使用。
顺便说一句,NSubstitute 将 mockReader.GetString(5).Returns(...)
视为与 mockReader.GetString(Arg.Is<int>(5))
相同,这将稍微简化您的代码。
另外,我建议尝试找到 use a real data reader in your tests 的方法。像下面这样的东西可以让你避免模拟出那种类型的细节:
DataTable dt = new DataTable();
dt.Columns.AddRange(
new [] {
new DataColumn("a", typeof(string)),
new DataColumn("b", typeof(string)),
new DataColumn("c", typeof(string)),
new DataColumn("d", typeof(string)),
new DataColumn("e", typeof(string)),
new DataColumn("f", typeof(string)),
new DataColumn("g", typeof(string))
});
dt.Rows.Add("11111", "AS3000-5/100-400-P", "10.42.42.26", "SIM-001", "12345lkj", "alibaba", "abrakadabra");
dt.Rows.Add("22222", "AS3000-5/100-400-Q", "10.42.42.158", null, null, null, null);
mockReader = new DataTableReader(dt);
然后您可以从测试中删除所有模拟设置。通过一些调整(比如,一些帮助方法来做一些事情,比如自动填充 reader 给定的预期对象,如 databaseDefinitionFirst
),这可以形成一种测试与 [=38 交互的所有代码的好方法=]s,而不是一直伪造一切。
我试图在我的 DAO class 中模拟 IDbConnection,但我收到以下错误:
字符串长度均为 5。字符串在索引 0 处不同。 预期:“11111” 但是是:“22222”
这是我的代码:
类 测试中
internal class ImportAcquisitionDataDAO : IImportAcquisitionDataDAO
{
private static Logger log = LogManager.GetCurrentClassLogger();
private readonly IDbConnection connection;
internal ImportAcquisitionDataDAO(IDbConnection connection)
{
this.connection = connection;
}
internal List<DefinitionEntry> GetDefinitions()
{
log.Debug("Getting definitions from database.");
var definitions = new List<DefinitionEntry>();
using (connection)
{
connection.Open();
log.Trace("Database connection opened");
IDbCommand command = connection.CreateCommand();
command.CommandText = @"SELECT *
FROM MAPPING";
IDataReader reader = command.ExecuteReader();
log.Trace("Command executed:\n{0}", command.CommandText);
definitions = GetMeterEntries(reader);
}
log.Debug("Obtained {0} definitions.", definitions.Count);
return definitions;
}
private List<DefinitionEntry> GetMeterEntries(IDataReader reader)
{
log.Trace("Parsing definitions from response");
var result = new List<DefinitionEntry>();
while (reader.Read())
{
var definition = new DefinitionEntry(
reader.GetString(0),
reader.GetString(1),
reader.GetString(2),
reader.IsDBNull(3) ? null : reader.GetString(3),
reader.IsDBNull(4) ? null : reader.GetString(4),
reader.IsDBNull(5) ? null : reader.GetString(5),
reader.IsDBNull(6) ? null : reader.GetString(6)
);
log.Trace(definition.ToString());
result.Add(definition);
}
return result;
}
}
public class DefinitionEntry
{
public string MeterSN { get; private set; }
public string MATNR { get; private set; }
public string IpAddress { get; private set; }
public string SIM { get; private set; }
public string ModulID { get; private set; }
public string SIMUser { get; private set; }
public string SIMPassword { get; private set; }
public DefinitionEntry(string meterSN, string matnr, string ipAddress, string sim, string modulId, string simUser, string simPassword)
{
MeterSN = meterSN;
MATNR = matnr;
IpAddress = ipAddress;
SIM = sim;
ModulID = modulId;
SIMUser = simUser;
SIMPassword = simPassword;
}
}
测试class
[TestFixture]
public class ImportAcquisitionDataDAOTests
{
private IDbConnection mockConnection;
private IDbCommand mockCommand;
private IDataReader mockReader;
private ImportAcquisitionDataDAO dao;
[SetUp]
public void SetUp()
{
mockConnection = Substitute.For<IDbConnection>();
mockCommand = Substitute.For<IDbCommand>();
mockReader = Substitute.For<IDataReader>();
dao = new ImportAcquisitionDataDAO(mockConnection);
mockConnection.CreateCommand().Returns(mockCommand);
mockCommand.ExecuteReader().Returns(mockReader);
}
[Test]
public void TestGetDefinitions()
{
// mock
var databaseDefinitionFirst = new DefinitionEntry("11111", "AS3000-5/100-400-P", "10.42.42.26", "SIM-001", "12345lkj", "alibaba", "abrakadabra");
var databaseDefinitionSecond = new DefinitionEntry("22222", "AS3000-5/100-400-Q", "10.42.42.158", null, null, null, null);
mockReader.Read().Returns(true, true, false);
mockReader.GetString(Arg.Is<int>(0)).Returns(databaseDefinitionFirst.MeterSN, databaseDefinitionSecond.MeterSN);
mockReader.GetString(Arg.Is<int>(1)).Returns(databaseDefinitionFirst.MATNR, databaseDefinitionSecond.MATNR);
mockReader.GetString(Arg.Is<int>(2)).Returns(databaseDefinitionFirst.IpAddress, databaseDefinitionSecond.IpAddress);
mockReader.IsDBNull(Arg.Is<int>(3)).Returns(false, true);
mockReader.GetString(Arg.Is<int>(3)).Returns(databaseDefinitionFirst.SIM);
mockReader.IsDBNull(Arg.Is<int>(4)).Returns(false, true);
mockReader.GetString(Arg.Is<int>(4)).Returns(databaseDefinitionFirst.ModulID);
mockReader.IsDBNull(Arg.Is<int>(5)).Returns(false, true);
mockReader.GetString(Arg.Is<int>(5)).Returns(databaseDefinitionFirst.SIMUser);
mockReader.IsDBNull(Arg.Is<int>(6)).Returns(false, true);
mockReader.GetString(Arg.Is<int>(6)).Returns(databaseDefinitionFirst.SIMPassword);
// use
List<DefinitionEntry> tested = dao.GetDefinitions();
// verify
Assert.AreEqual(2, tested.Count);
AssertDefinitionEntry(databaseDefinitionFirst, tested.First());
AssertDefinitionEntry(databaseDefinitionSecond, tested.Last());
}
private void AssertDefinitionEntry(DefinitionEntry expected, DefinitionEntry tested)
{
Assert.AreEqual(expected.MeterSN, tested.MeterSN);
Assert.AreEqual(expected.MATNR, tested.MATNR);
Assert.AreEqual(expected.IpAddress, tested.IpAddress);
Assert.AreEqual(expected.SIM, tested.SIM);
Assert.AreEqual(expected.ModulID, tested.ModulID);
Assert.AreEqual(expected.SIMUser, tested.SIMUser);
Assert.AreEqual(expected.SIMPassword, tested.SIMPassword);
}
}
这是执行第一个 Assert 之前的调试屏幕: enter image description here
除 MeterSN 字段外,我的所有设置均符合我的预期,这两个条目都相同。但我无法找出原因
这是一个相当微妙的问题。
简答:
将 mockReader.GetString(Arg.Is<int>(0)).Returns(databaseDefinitionFirst.MeterSN, databaseDefinitionSecond.MeterSN);
移动到最后一个被存根的调用(就在 mockReader.GetString(Arg.Is<int>(6))
之下)。
更长的答案:
当我们说 mockReader.GetString(Arg.Is<int>(5))
时,Arg.Is
部分 returns 为零,这意味着 mockReader.GetString(0)
在测试设置中被多次调用。所以“11111”确实首先按照配置返回,但是当 GetDefinitions
被调用时,该值已经被使用。
顺便说一句,NSubstitute 将 mockReader.GetString(5).Returns(...)
视为与 mockReader.GetString(Arg.Is<int>(5))
相同,这将稍微简化您的代码。
另外,我建议尝试找到 use a real data reader in your tests 的方法。像下面这样的东西可以让你避免模拟出那种类型的细节:
DataTable dt = new DataTable();
dt.Columns.AddRange(
new [] {
new DataColumn("a", typeof(string)),
new DataColumn("b", typeof(string)),
new DataColumn("c", typeof(string)),
new DataColumn("d", typeof(string)),
new DataColumn("e", typeof(string)),
new DataColumn("f", typeof(string)),
new DataColumn("g", typeof(string))
});
dt.Rows.Add("11111", "AS3000-5/100-400-P", "10.42.42.26", "SIM-001", "12345lkj", "alibaba", "abrakadabra");
dt.Rows.Add("22222", "AS3000-5/100-400-Q", "10.42.42.158", null, null, null, null);
mockReader = new DataTableReader(dt);
然后您可以从测试中删除所有模拟设置。通过一些调整(比如,一些帮助方法来做一些事情,比如自动填充 reader 给定的预期对象,如 databaseDefinitionFirst
),这可以形成一种测试与 [=38 交互的所有代码的好方法=]s,而不是一直伪造一切。