Методы тестирования единиц с помощью файла IO

Я пытаюсь привыкнуть к написанию юнит-тестов, я уже писал несколько ранее, но они обычно были довольно простыми... Я хотел бы начать переходить на TDD, так как хочу улучшить качество моего кода (дизайн и структура) - уменьшение связи, в то же время, мы надеемся, уменьшить количество регрессий, которые переходят в тестируемую сборку.

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

Вот типичный пример некоторого кода, извлеченного из проекта:

private string RestoreExtension(String file)
    {
        var unknownFile = Path.GetFileName(file);
        var ignoreDir = Path.GetDirectoryName(file) + "\\Unknown";
        string newFile;

        //We need this library for determining mime
        if (CheckLibrary("urlmon.dll"))
        {
            AddLogLine("Attempting to restore file extension");
            var mime = GetMimeType(BufferFile(file, 256));
            var extension = FileExtensions.FindExtension(mime);
            if (!String.IsNullOrEmpty(extension))
            {
                AddLogLine("Found extension: " + extension);
                newFile = file + "." + extension;
                File.Move(file, newFile);
                return newFile;
            }
        }
        else
        {
            AddLogLine("Unable to load urlmon.dll");
        }

        AddLogLine("Unable to restore file extension");

        if (!Directory.Exists(ignoreDir))
        {
            Directory.CreateDirectory(ignoreDir);
        }
        var time = DateTime.Now;
        const string format = "dd-MM-yyyy HH-mm-ss";

        newFile = ignoreDir + "\\" + unknownFile + "-" + time.ToString(format);
        File.Move(file, newFile);

        return String.Empty;
    }

Вопросы:

Как я могу проверить метод, используя IO? Я действительно не хочу использовать настоящий файл, так как это будет медленным (и больше интеграционным тестом?), Но я действительно не вижу другого пути. Я мог бы добавить переключаемый слой абстракции ввода/вывода, но для меня это звучит так, как будто это может излишне усложнить код...

Стоит ли проводить подобный тестовый проект? Я имею в виду, это слишком просто. Как только вы убираете вызовы из .Net и сторонних библиотек, осталось не так уж много... Так значит ли это, что объем работы по его тестированию означает, что он не является хорошим кандидатом для тестирования?

Многие методы в этом проекте являются частными, так как этот проект является службой Windows. Я читал, что вам следует проверять только те методы, которые внешне видимы (общедоступны или доступны через интерфейс), однако в этом случае маловероятно, что я захочу предоставить какой-либо из моих собственных методов. Так стоит ли тестировать этот проект (поскольку id, вероятно, нужно либо изменить общедоступные модификаторы методов, либо добавить некоторые атрибуты, чтобы их мог видеть NUnit)?

Ответ 1

Относительно того, как тестировать ввод/вывод файлов: Общим способом сделать это является инкапсуляция ввода-вывода файлов в класс-оболочку. Например:

public class FileHandler : IFileHandler
{
     public string GetFilename(string name)
     {
         return System.IO.GetFileName(name);
     }

    public string GetDirectoryName(string directory)
    {
         return System.IO.GetDirectoryName(directory);
    }
}

public interface IFileHandler
{
      string GetFilename(string name);
      string GetDirectoryName(string directory);
}

В вашем тестируемом методе вы только программируете против интерфейса. Когда вы запустите свой unit test, вы будете использовать макетный объект, который возвращает предопределенные значения. Для этого вы можете использовать Moq.

Это небольшая работа, но вам также не нужно тестировать файлы ввода/вывода.

Лучший Ян

Ответ 2

Как я могу проверить метод с использованием IO?

Введя абстракцию над библиотеками .NET IO, а затем используя реальную реализацию (BCL) в приложении и поддельную (издеваемую) в unit test. Такая абстракция достаточно проста для написания самостоятельно, но эти проекты уже существуют (например, System.IO.Abstraction), поэтому изобретать колесо бессмысленно, особенно в такой тривиальный вопрос.

Я действительно не хочу использовать реальный файл, так как это будет медленным

Это правильно, в тесте вы хотите использовать поддельные (mock) объекты.

Является ли проект подходящим модульным тестированием?

Каждый проект стоит протестировать, и вы будете тестировать его так или иначе (в крайнем случае, вручную выполняя приложение и делая хорошее, старое тестирование "щелчок и ожидание" ).

Обратите внимание, что почти всегда слишком много времени для ручного тестирования 10 краевых случаев сложного метода парсера, который каждый раз требует, чтобы вы загружали большой файл и ожидали результатов. Это помогает автоматизировать тесты в изолированной среде. То же самое происходит с вашей кодовой файловой системой, веб-службой, все они вводят большие накладные расходы, прежде чем вы сможете перейти к реальной бизнес-логике. Необходимо изолировать.

Многие из методов в этом проекте являются частными (...). Я читал, что вы должны действительно проверять методы, которые внешне видимы

Правильно, вы должны проверить публичный контракт. Однако, если частные вещи становятся достаточно большими, чтобы проверить его, в большинстве случаев это хороший показатель, он также достаточно велик, чтобы помещать его в свой класс. Обратите внимание, что изменение модификаторов доступа для членов только для использования unit test не является лучшей идеей. Почти всегда лучше извлекать класс/метод рефакторинга.

Ответ 3

Я тоже довольно новичок в TDD, но я думаю, что могу предложить вам несколько советов:

  • О тестировании IO. Вы можете использовать макет объектов. Создайте макет для метода, который вы хотите протестировать, и сравните его с реальным переданным значением. В Moq Framework это выглядит так:

    var mock = new Mock<IRepository<PersonalCabinetAccount>>();
    mock.Setup(m => m.RemoveByID(expectedID))
        .Callback((Int32 a) => Assert.AreEqual(expectedID, a));
    
    var account = new PersonalCabinetAccount {ID = myID};
    var repository = mock.Object;
    repository.RemoveByID(account.myId);
    
  • Абсолютно, да. Если вы хотите получить привычку, вам нужно все проверить, я полагаю. Таким образом, знание приходит;)

  • Вы можете использовать атрибут InternalsVisibleTo, чтобы избежать проблем с видимостью.

    методы проверки, которые внешне видимы

Я думаю, что это касается корпоративного программирования. просто запасное время с вашим собственным доверенным кодом.

Надеюсь, моя точка может вам помочь.

Ответ 4

Вы можете сделать это, используя Microsoft Fakes. Сначала создать фальшивую сборку для System.dll - или любой другой пакет, а затем высмеять ожидаемые результаты, как в:

using Microsoft.QualityTools.Testing.Fakes;
...
using (ShimsContext.Create())
{
     System.IO.Fakes.ShimDirectory.ExistsString = (p) => true;
     System.IO.Fakes.ShimFile.MoveStringString = (p,n) => {};
     // .. and other methods you'd like to test

     var result = RestoreExtension("C:\myfile.ext");
     // then you can assert with this result 
}