为什么 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,而不是一直伪造一切。