Лучший способ модульного тестирования классов с использованием потоков? - PullRequest
0 голосов
/ 11 декабря 2018

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

1) Концепт один хранит сериализацию отдельно от типа хранилища

2) Концепция два хранит тип хранилища отдельно от стратегии сериализации.

Я нашел хороший способ, который работает, проводя некоторые исследования в различных потоках, такие как использование TextWriter / TextReader вместо прямого использования файлов, чтобы можно было использовать любой тип потока (FileStream / MemoryStream / и т. Д.), Чтобы модульные тестыможно сделать без файлов.Однако я столкнулся с проблемой, поскольку классы TextWriter / TextReader, которые обертывают потоки, автоматически закрывают и удаляют потоки, когда они сами расположены, что я и хочу на практике, но застревает в модульном тестировании.

Вот код, который у меня есть до сих пор ... это для концепции 1, процесс сериализации.Вот интерфейсы для него:

/// <summary>
/// Interface for a serializer which reads from a stream and creates a type
/// </summary>
public interface IInSerializer
{
    /// <summary>
    /// Load type from a stream
    /// </summary>
    /// <param name="reader"></param>
    /// <returns></returns>
    bool Load(TextReader reader);
}

/// <summary>
/// Interface for writing a type out into a stream
/// </summary>
public interface IOutSerializer
{
    /// <summary>
    /// Save to the stream
    /// </summary>
    /// <param name="writer"></param>
    /// <returns></returns>
    bool Save(TextWriter writer);
}

/// <summary>
/// Helper interface which provides interface <see cref="IInSerializer"/> 
/// and <see cref="IOutSerializer"/> for both reading/writing
/// </summary>
public interface IInOutSerializer : IInSerializer, IOutSerializer
{
}

Вот абстрактная реализация сериализатора для формата JSON:

/// <summary>
/// Implementation of <see cref="IInOutSerializer"/> which serializes into JSON format
/// </summary>
/// <typeparam name="T">Type to be serialized</typeparam>
public abstract class JSONSerializer<T> : IInOutSerializer
{
    /// <summary>
    /// Source of serialization
    /// </summary>
    public T Source { get; set; }

    /// <summary>
    /// Provided by very specific type to load the Jobject into type T
    /// </summary>
    /// <param name="jObject"></param>
    /// <returns></returns>
    protected abstract bool LoadJObject(JObject jObject);
    /// <summary>
    /// Provided by very specific type to save type T into a Jobject
    /// </summary>
    /// <param name="jObject"></param>
    /// <returns></returns>
    protected abstract bool Serialize(JObject jObject);

    /// <summary>
    /// <see cref="IInOutSerializer.Load"/>
    /// </summary>
    /// <param name="reader"></param>
    /// <returns></returns>
    public bool Load(TextReader reader)
    {
        using (var json = new JsonTextReader(reader))
        {
            var jObject = JToken.ReadFrom(json) as JObject;
            if (jObject != null)
                return LoadJObject(jObject);
        }
        return false;
    }

    /// <summary>
    /// <see cref="IInOutSerializer.Save"/>
    /// </summary>
    /// <param name="writer"></param>
    /// <returns></returns>
    public bool Save(TextWriter writer)
    {
        var jObject = new JObject();
        if (Serialize(jObject))
        {
            using (var json = new JsonTextWriter(writer))
            {
                json.Formatting = Formatting.Indented;
                jObject.WriteTo(json);
                return true;
            }
        }
        return false;
    }
}

А вот один из конкретных типов для сериализации моего класса MetroLineDetails:

public class MetroLineJSONSerializationStrategy : JSONSerializer<MetroLineDetails>
{
    private class MetroLineHelper : IMetroLine, IMetroLineWritable
    {
        public string DestinationStation
        {
            get;
            set;
        }

        public Color LineColor
        {
            get;
            set;
        }

        public char LineLetter
        {
            get;
            set;
        }

        public string Name
        {
            get;
            set;
        }

        public bool SaturdayService
        {
            get;
            set;
        }

        public string SourceStation
        {
            get;
            set;
        }

        public bool SundayHolidayService
        {
            get;
            set;
        }

        public static explicit operator MetroLineDetails(MetroLineHelper source)
        {
            return new MetroLineDetails(source.Name, source.LineColor, source.SourceStation, source.DestinationStation, source.SaturdayService, source.SundayHolidayService);
        }
    }
    protected override bool LoadJObject(JObject jObject)
    {
        var helper = new MetroLineHelper();
        jObject.Read(nameof(MetroLineDetails.Name), (t) => (string)t, (v) => helper.Name = v);
        jObject.Read(nameof(MetroLineDetails.LineLetter), (t) => (char)t, (v) => helper.LineLetter = v);
        jObject.Read(nameof(MetroLineDetails.SourceStation), (t) => (string)t, (v) => helper.SourceStation = v);
        jObject.Read(nameof(MetroLineDetails.DestinationStation), (t) => (string)t, (v) => helper.DestinationStation = v);
        jObject.Read(nameof(MetroLineDetails.SaturdayService), (t) => (bool)t, (v) => helper.SaturdayService = v);
        jObject.Read(nameof(MetroLineDetails.SundayHolidayService), (t) => (bool)t, (v) => helper.SundayHolidayService = v);

        var color = jObject.Read(nameof(MetroLineDetails.LineColor), (t) => (JObject)t);
        helper.LineColor = color.ToColor();

        Source = (MetroLineDetails)helper;

        return true;
    }

    protected override bool Serialize(JObject jObject)
    {
        jObject.Add(nameof(MetroLineDetails.Name), Source.Name);
        jObject.Add(nameof(MetroLineDetails.LineLetter), Source.LineLetter);
        jObject.Add(nameof(MetroLineDetails.SourceStation), Source.SourceStation);
        jObject.Add(nameof(MetroLineDetails.DestinationStation), Source.DestinationStation);
        jObject.Add(nameof(MetroLineDetails.SaturdayService), Source.SaturdayService);
        jObject.Add(nameof(MetroLineDetails.SundayHolidayService), Source.SundayHolidayService);
        jObject.Add(nameof(MetroLineDetails.LineColor), Source.LineColor.ToJObject());

        return true;
    }
}

А теперь вот мои интерфейсы типов хранения:

/// <summary>
/// Interface for the storage medium
/// </summary>
public interface IStorageMedium
{
    /// <summary>
    /// Save the information in the serializer
    /// </summary>
    /// <param name="serializer"></param>
    void Save(IOutSerializer serializer);
    /// <summary>
    /// Load the information to the serializer
    /// </summary>
    /// <param name="serializer"></param>
    void Load(IInSerializer serializer);
}

И тип специально для файлов:

/// <summary>
/// Implementation of <see cref="IStorageMedium"/> which stores into a file
/// </summary>
public class FileStorageMedium : IStorageMedium
{
    private readonly string _fileName;

    public FileStorageMedium(string fileName)
    {
        _fileName = fileName;
    }

    public void Save(IOutSerializer serializer)
    {
        using (var stream = new FileStream(_fileName, FileMode.Truncate))
        {
            using (var writer = new StreamWriter(stream))
            {
                serializer.Save(writer);
            }
        }
    }

    public void Load(IInSerializer serializer)
    {
        using (var stream = new FileStream(_fileName, FileMode.Open))
        {
            using (var reader = new StreamReader(stream))
            {
                serializer.Load(reader);
            }
        }
    }
}

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

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

[TestClass]
public class MetroLine
{
    [TestMethod]
    public void TestSerialize()
    {
        var serializer = new MetroLineJSONSerializationStrategy();
        serializer.Source = new MetroLineDetails("A", Colors.Blue, "LA Union Station", "San Bernardino", true, true);
        using (var stream = new MemoryStream())
        {
            var writer = new StreamWriter(stream);
            serializer.Save(writer);
            stream.Seek(0, SeekOrigin.Begin);
            using (var reader = new StreamReader(stream))
            {
                var text = reader.ReadToEnd();
            }
        }
    }
}

Поток закрывается независимо от того, что я делаю в вызове serializer.Save (), так как этот метод использует одноразовый метод, который закрывает поток (как ясчитаю, что это должно предотвратить утечки).Проблема в том, что я больше не могу тестировать поток каким-либо образом, чтобы проверить, работает ли что-то из этого.Я получаю исключения, говорящие, что вы больше не можете получить доступ к закрытым потокам, что имеет смысл.Но как я могу проверить содержимое моего потока любым значимым способом?

1 Ответ

0 голосов
/ 11 декабря 2018

Я нашел GetBuffer в MemoryStream, который позволяет мне преобразовывать необработанный буфер в строку, и я могу тестировать блок JSON как угодно, как я хочу ... вот что я написал:

    [TestMethod]
    public void TestSerialize()
    {
        var serializer = new MetroLineJSONSerializationStrategy();
        serializer.Source = new MetroLineDetails("Inland Empire Line", Colors.Blue, 'A', "LA Union Station", "San Bernardino", true, true);
        using (var stream = new MemoryStream())
        {
            using (var writer = new StreamWriter(stream))
            {
                serializer.Save(writer);
            }
            var bytes = stream.GetBuffer();
            var json = System.Text.Encoding.UTF8.GetString(bytes);
            Assert.AreEqual('{', json[0]);
        }
    }

Я надеюсь, что кто-то найдет это полезным!

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