Mocking StreamReader в C # для модульного теста - PullRequest
2 голосов
/ 03 апреля 2019

У меня есть код этого формата:

public void parseFile(string filePath)
{
    using (var reader = new StreamReader(@filePath))
    {
        //Do something
    }
}

Теперь я хочу выполнить модульное тестирование своего кода, но не хочу, чтобы мои модульные тесты действительно обращались к файловой системе, поскольку это сделает их ненадежными, еслиреальных файлов там нет.

Чтобы предотвратить доступ потокового считывателя к файловой системе, мне нужно было бы сделать это.Насколько я понимаю, я могу только издеваться над классом, который реализует интерфейс.

Итак, одно решение, которое я вижу, - это создание класса-оболочки и интерфейса для Streamreader, которые я затем могу смоделировать.

Мой вопрос состоит из 2 частей:

  1. Это единственное решение этой проблемы?

  2. Было бы правильно добавить дополнительнуюслой в моем проекте, чтобы облегчить юнит-тест?Если бы я следовал этому принципу глобально, я мог бы добавить много дополнительных классов?

Ответы [ 2 ]

6 голосов
/ 03 апреля 2019

Проблема с приведенным выше примером заключается в том, что метод parseFile создает свой собственный экземпляр StreamReader, поэтому насмешка над StreamReader фактически не будет работать, потому что:

  1. У вас нет доступа к объекту.
  2. Вы можете издеваться только над interfaces или членами классов, помеченных как virtual.

Вместо этого вы можете создать интерфейс, назовем его IFileManager для аргументов, с помощью метода StreamReader.

public interface IFileManager
{
     StreamReader StreamReader(string path);
}

Затем в другом классе (назовем его Foo), который содержит метод ParseFile, который вы опубликовали выше:

public class Foo 
{
    IFileManager fileManager;

    public Foo(IFileManager fileManager)
    {
        this.fileManager = fileManager;
    }

    public void parseFile(string filePath)
    {
        using (var reader = fileManager.StreamReader(filePath))
        {
            //Do something
        }
    }
}

Теперь вы можете смоделировать IFileManager interface и его метод StreamReader, вы можете добавить этот смоделированный экземпляр в класс Foo, сделав его доступным для использования методом ParseFile.

Теперь ваш код будет зависеть от абстракции, в отличие от конкретной реализации, мы инвертировали зависимость, что позволяет нам имитировать зависимости и изолировать код, который мы хотим на самом деле протестировать.


Грубая демонстрация создания фиктивного объекта

public static void Main()
{
    Mock<IFileManager> mockFileManager = new Mock<IFileManager>();
    string fakeFileContents = "Hello world";
    byte[] fakeFileBytes = Encoding.UTF8.GetBytes(fakeFileContents);

    MemoryStream fakeMemoryStream = new MemoryStream(fakeFileBytes);

    mockFileManager.Setup(fileManager => fileManager.StreamReader(It.IsAny<string>()))
                   .Returns(() => new StreamReader(fakeMemoryStream));

    Foo foo = new Foo(mockFileManager.Object);

    string result = foo.ParseFile("test.txt");

    Console.WriteLine(result);
}

public interface IFileManager
{
    StreamReader StreamReader(string path);
}

public class Foo
{
    IFileManager fileManager;

    public Foo(IFileManager fileManager)
    {
        this.fileManager = fileManager;
    }

    public string ParseFile(string filePath)
    {
        using (var reader = fileManager.StreamReader(filePath))
        {
            return reader.ReadToEnd();
        }
    }
}
0 голосов
/ 03 апреля 2019

Взгляните на System.IO.Abstractions - это в значительной степени позволяет вам высмеивать пространство имен System.IO .net для ваших тестов

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...