模拟StreamReader

Han*_*del 3 c# nunit unit-testing moq c#-4.0

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

用于抽象StreamReader的类/接口

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

public class PathReader : IPathReader
{
    public TextReader CreateReader(string filePath)
    {
        return new StreamReader(filePath);
    }
}
Run Code Online (Sandbox Code Playgroud)

包含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;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

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

问题

  1. CreateReader的返回类型应该是TextReader吗?
  2. 在将其分配给rowReader之前,是否需要将返回的TextReader显式转换回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);
    }
Run Code Online (Sandbox Code Playgroud)

我假设有基本的,我缺少所以id非常感谢一些帮助.谢谢你的时间.

编辑

测试方法:

    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);
    }
Run Code Online (Sandbox Code Playgroud)

存根声明:

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

编辑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);
    }
}
}
Run Code Online (Sandbox Code Playgroud)

测试方法(适用于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);
    }
Run Code Online (Sandbox Code Playgroud)

Jon*_*eet 9

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

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

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

当你返回a时StringReader,它确实会在它被强制转换时(或者在模拟框架本身中)引起异常.不幸的是,您的异常处理过于宽泛,因此很难看到该问题.你的catch街区应该只能抓住IOException- 如果确实有的话.(如果无法读取资源,你真的想要返回-1吗?为什么不让异常冒泡?)捕获Exception应该非常罕见 - 基本上只在宏操作的顶层(例如Web服务)请求)避免在可以继续执行其他操作时终止进程.