3

我有一个使用 StreamReader 的方法,我想对其进行单元测试。我将 StreamReader 的创建拆分为一个单独的类并尝试模拟该类,但我的单元测试仍然给我错误。

用于抽象 StreamReader 的类/接口

public interface IPathReader
{
    TextReader CreateReader(string path);
}

public class PathReader : IPathReader
{
    public TextReader CreateReader(string filePath)
    {
        return new StreamReader(filePath);
    }
}

包含 GetLastRowInFile 的类(我试图进行单元测试的方法)。

public interface IIOManager
{
    int GetLastRowInFile(string filePath, List<String> errorMessageList);
}


public class IOManager : IIOManager
{
    private IPathReader _reader;

    public IOManager(IPathReader reader)
    {
        this._reader = reader;
    }

    //...

    public int GetLastRowInFile(string filePath, List<String> errorMessage)
    {
        int numberOfRows = 0;
        string dataRow;

        try
        {
            using (StreamReader rowReader = (StreamReader)_reader.CreateReader(filePath))
            {
                while ((rowReader.Peek()) > -1)
                {
                    dataRow = rowReader.ReadLine();
                    numberOfRows++;
                }
                return numberOfRows;
            }
        }
        catch (Exception ex)
        {
            errorMessage.Add(ex.Message);
            return -1;
        }
    }
}

StreamReader 不包含默认构造函数,所以我不相信我可以直接模拟它,因此需要从 GetLastRowInFile 中创建 StreamReader。

问题

  1. CreateReader 的返回类型应该是 TextReader 吗?
  2. 在将返回的 TextReader 分配给 rowReader 之前,我是否需要将其显式转换回 StringReader?
  3. 当我创建 IPathReader 接口的模拟并将 CreateReader 设置为返回 StringReader 时,当它被分配给 rowReader 时会发生什么。我认为不可能在相同的继承级别上投射一些东西?

继承层次

单元测试如下,它一直返回-1

    [Test]
    public void GetLastRowInFile_ReturnsNumberOfRows_Returns3()
    {
        string testString = "first Row" + Environment.NewLine + "second Line" + Environment.NewLine + "Third line";
        List<String> errorMessageList = new List<string>();

        Mock<IPathReader> mock = new Mock<IPathReader>();
        mock.Setup(x => x.CreateReader(It.IsAny<string>())).Returns(new StringReader(testString));

        IOManager testObject = new IOManager(mock.Object);

        int i = testObject.GetLastRowInFile(testString, errorMessageList);              //Replace with It.IsAny<string>()
        Assert.AreEqual(i, 3);
        Assert.AreEqual(errorMessageList.Count, 0);
    }

我假设我缺少一些基本的东西,所以我真的很感谢你的帮助。谢谢你的时间。

编辑

测试方法:

    public void GetLastRowInFile_ReturnsNumberOfRows_Returns3()
    {
        StubGetLastRowInFile myStub = new StubGetLastRowInFile();
        List<String> errorMessageList = new List<string>();
        IOManager testObject = new IOManager(myStub);
        int i = testObject.GetLastRowInFile(It.IsAny<string>(), errorMessageList);
        Assert.AreEqual(i, 3);
        Assert.AreEqual(errorMessageList.Count, 0);
    }

存根声明:

public class StubGetLastRowInFile : IPathReader
{
    public TextReader CreateReader(string path)
    {
        //string testString = "first Row" + Environment.NewLine + "second Line" + Environment.NewLine + "Third line";
        string testString = "04/01/2010 00:00,1.4314,1.4316";
        UTF8Encoding encoding = new UTF8Encoding();
        UnicodeEncoding uniEncoding = new UnicodeEncoding();

        byte[] testArray = encoding.GetBytes(testString);

        MemoryStream ms = new MemoryStream(testArray);

        StreamReader sr = new StreamReader(ms);

        return sr;
    }
}

编辑 2

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Windows.Forms;
using System.Data;
using System.Globalization;
using System.Collections;
using System.Reflection;
using System.ComponentModel;

namespace FrazerMann.CsvImporter.Entity
{
    public interface IPathReader
    {
        TextReader CreateReader(string path);
    }

    public class PathReader : IPathReader
    {
        public TextReader CreateReader(string filePath)
        {
            return new StreamReader(filePath);
        }
    }


public interface IIOManager
{
    Stream OpenFile(string path);

    int GetLastRowInFile(string filePath, List<String> errorMessageList);

    int GetNumberOfColumnsInFile(string filePath, List<string> errorMessageList);

    bool IsReadOnly(string filePath);
}


public class IOManager : IIOManager
{
    private IPathReader _reader;

    public IOManager(IPathReader reader)
    {
        this._reader = reader;
    }


    public Stream OpenFile(string path)
    {
        return new FileStream(path, FileMode.Open);
    }


    public int GetNumberOfColumnsInFile(string filePath, List<String> errorMessageList)
    {
        int numberOfColumns = 0;
        string lineElements;

        try
        {
            using (StreamReader columnReader = (StreamReader)_reader.CreateReader(filePath))
            {
                lineElements = columnReader.ReadLine();
                string[] columns = lineElements.Split(',');
                numberOfColumns = columns.Length;
            }
        }
        catch (Exception ex)
        {
            errorMessageList.Add(ex.Message);
            numberOfColumns = -1;
        }
        return numberOfColumns;
    }


    public int GetLastRowInFile(string filePath, List<String> errorMessage)
    {
        int numberOfRows = 0;
        string dataRow;

        try
        {
            using (StreamReader rowReader = (StreamReader)_reader.CreateReader(filePath))
            {
                while ((rowReader.Peek()) > -1)
                {
                    dataRow = rowReader.ReadLine();
                    numberOfRows++;
                }
                return numberOfRows;
            }
        }
        catch (Exception ex)
        {
            errorMessage.Add(ex.Message);
            return -1;
        }
    }


    public bool IsReadOnly(string filePath)
    {
        FileInfo fi = new FileInfo(filePath);
        return fi.IsReadOnly;
    }
}


public interface IVerificationManager
{
    void ValidateCorrectExtension(string filePath, List<String> errorMessageList);

    void ValidateAccessToFile(string filePath, List<String> errorMessageList);

    void ValidateNumberOfColumns(string filePath, int dataTypeCount, List<String> errorMessageList);

    int ValidateFinalRow(int finalRow, string filePath, List<String> errorMessageList);

    void ValidateRowInputOrder(int initialRow, int finalRow, List<String> errorMessageList);

    void EnumeratedDataTypes(UserInputEntity inputs, List<String> errorMessageList);

    int GetProgressBarIntervalsForDataVerification(int initialRow, int finalRow, List<String> errorMessageList);
}


public class VerificationManager : IVerificationManager
{
    private IIOManager _iomgr;

    public VerificationManager(IIOManager ioManager)
    {
        this._iomgr = ioManager;
    }

    public void ValidateCorrectExtension(string filePath, List<String> errorMessageList)
    {
        if (filePath.EndsWith(".txt", StringComparison.OrdinalIgnoreCase) | filePath.EndsWith(".csv", StringComparison.OrdinalIgnoreCase)) { }
        else
        {
            errorMessageList.Add("Selected file does not have a compatable extension.");
        }
    }

    public void ValidateAccessToFile(string filePath, List<String> errorMessageList)
    {
        try
        {

            if (_iomgr.IsReadOnly(filePath) == true) { }
            else
            {
                errorMessageList.Add("Can not read/write to the specified file.");
            }
        }
        catch (Exception e)
        {
            errorMessageList.Add(e.Message);
        }
    }

    public void ValidateNumberOfColumns(string filePath, int userSpecifiedColumnCount, List<String> errorMessageList)
    {
        int numberOfColumnsInFile = _iomgr.GetNumberOfColumnsInFile(filePath, errorMessageList);

        if (userSpecifiedColumnCount != numberOfColumnsInFile) errorMessageList.Add("Number of columns specified does not match number present in file.");
    }

//**TEST APPLIES HERE**

    public int ValidateFinalRow(int finalRow, string filePath, List<String> errorMessageList)
    {
        int totalNumberOfRowsInFile = 0;

        totalNumberOfRowsInFile = _iomgr.GetLastRowInFile(filePath, errorMessageList);

        if (totalNumberOfRowsInFile != -1)
        {
            if (finalRow == 0)
            {
                return totalNumberOfRowsInFile;
            }
            else
            {
                if (finalRow > totalNumberOfRowsInFile)
                {
                    errorMessageList.Add("Specified 'Final Row' value is greater than the total number of rows in the file.");
                }
            }
        }
        return 0;
    }

    public void ValidateRowInputOrder(int initialRow, int finalRow, List<String> errorMessageList)
    {
        if (initialRow > finalRow)
        {
            errorMessageList.Add("Initial row is greater than the final row.");
        }
    }

    public void EnumeratedDataTypes(UserInputEntity inputs, List<String> errorMessageList)
    {
        inputs.EnumeratedDataTypes = new int[inputs.DataTypes.Count];
        try
        {
            for (int i = 0; i < inputs.DataTypes.Count; i++)
            {
                inputs.EnumeratedDataTypes[i] = (int)Enum.Parse(typeof(Enumerations.ColumnDataTypes), inputs.DataTypes[i].ToUpper());
            }
        }
        catch (Exception ex)
        {
            errorMessageList.Add(ex.Message);
        }
    }

    public int GetProgressBarIntervalsForDataVerification(int initialRow, int finalRow, List<String> errorMessageList)
    {
        int progressBarUpdateInverval = -1;

        try
        {
            int dif = (finalRow - initialRow) + 1;
            progressBarUpdateInverval = dif / 100;

            if (progressBarUpdateInverval == 0)
            {
                progressBarUpdateInverval = 1;
            }
        }
        catch (Exception ex)
        {
            errorMessageList.Add(ex.Message);
        }
        return progressBarUpdateInverval;
    }
}



public class EntityVerification
{

    private VerificationManager _vmgr;

    public EntityVerification(VerificationManager vManager)
    {
        this._vmgr = vManager;
    }


    public void VerifyUserInputManager(UserInputEntity inputs, List<string> errorMessageList)
    {
        _vmgr.ValidateCorrectExtension(inputs.CsvFilePath ,errorMessageList);
        _vmgr.ValidateCorrectExtension(inputs.ErrorLogFilePath, errorMessageList);

        _vmgr.ValidateAccessToFile(inputs.CsvFilePath, errorMessageList);
        _vmgr.ValidateAccessToFile(inputs.ErrorLogFilePath, errorMessageList);

        _vmgr.ValidateNumberOfColumns(inputs.CsvFilePath, inputs.DataTypes.Count, errorMessageList);

        inputs.FinalRow = _vmgr.ValidateFinalRow(inputs.FinalRow, inputs.CsvFilePath, errorMessageList);

        _vmgr.ValidateRowInputOrder(inputs.InitialRow, inputs.FinalRow, errorMessageList);

        _vmgr.EnumeratedDataTypes(inputs, errorMessageList);

        inputs.ProgressBarUpdateIntervalForDataVerification = _vmgr.GetProgressBarIntervalsForDataVerification(inputs.InitialRow, inputs.FinalRow, errorMessageList);
    }
}
}

测试方法(适用于 VerificationManager 类中倒数第三个方法)

    [Test]
    public void ValidateFinalRow_FinalRowReturned_Returns6()
    {
        List<String> errorMessageList = new List<string>();                             //Remove if replaced

        Mock<IIOManager> mock = new Mock<IIOManager>();
        mock.Setup(x => x.GetLastRowInFile(It.IsAny<String>(), errorMessageList)).Returns(6);

        VerificationManager testObject = new VerificationManager(mock.Object);
        int i = testObject.ValidateFinalRow(0, "Random", errorMessageList);             //Replace with It.IsAny<string>()  and It.IsAny<List<string>>()
        Assert.AreEqual(i, 6);
    }
4

1 回答 1

9

目前尚不清楚为什么要在这里使用模拟。

是的,使用TextReader而不是要求StreamReader会给你更多的灵活性。很少有明确指定StreamReader为参数或返回类型的情况。

如果要为 a 提供测试数据StreamReader,只需创建一个StreamReader包装 aMemoryStream

当您返回 aStringReader时,它确实会在强制转换时(或在模拟框架本身中)导致异常。不幸的是,您的异常处理过于广泛,因此很难看到这个问题。你的catch块应该捕获IOException- 如果确实有的话。(如果无法读取资源,您真的要返回 -1 吗?为什么不让异常冒泡?)捕获Exception应该非常罕见 - 基本上只在大操作的顶层(例如 Web 服务request) 以避免在进程可以继续其他操作时杀死进程。

于 2012-08-09T09:13:28.667 回答