简体   繁体   English

使用流对类进行单元测试的最佳方法?

[英]Best way to unit test classes using streams?

I am currently writing some code to try to experiment with separating and abstracting two parts of our storage strategies at work. 我目前正在编写一些代码,以尝试对工作中的存储策略的两个部分进行分离和抽象。 We currently use JSON format stored into a file and then retrieve it as our persistent storage. 当前,我们使用JSON格式存储到文件中,然后将其检索为持久存储。 I am trying to experiment with separating the two concepts: 我正在尝试将两个概念分开:

1) Concept one keeps the serialization separate from the storage type 1)概念一将序列化与存储类型分开

2) Concept two keeps the storage type separate from the serialization strategy. 2)概念二将存储类型与序列化策略分开。

I found a good way that works doing some research on various threads, such as using TextWriter/TextReader instead of directly using Files so that any Stream type can be used (FileStream/MemoryStream/etc) so that the unit tests can be done without files. 我找到了一种在各种线程上进行研究的好方法,例如使用TextWriter / TextReader而不是直接使用Files,以便可以使用任何Stream类型(FileStream / MemoryStream / etc),从而可以在没有文件的情况下进行单元测试。 。 However, I am running into a problem since the TextWriter/TextReader classes which wrap the streams automatically close and dispose of the streams when they are themselves disposed, which is what I want in practice, but gets me stuck in unit testing. 但是,我遇到了一个问题,因为包装流的TextWriter / TextReader类在流本身被处置时会自动关闭并处置,这是我在实践中想要的,但是却使我陷入了单元测试的困境。

Here is the code I have so far... this is for concept 1, the serialization process. 这是我到目前为止的代码...这是针对概念1(序列化过程)的。 Here are the interfaces for it: 这是它的接口:

/// <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
{
}

Here is an abstract implementation of the serializer for JSON format: 这是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;
    }
}

And here is one of the concrete types for serializing my class MetroLineDetails: 这是序列化我的Metro MetroDetails类的具体类型之一:

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;
    }
}

And now here are my storage type interfaces: 现在这是我的存储类型接口:

/// <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);
}

And the type specifically for files: 以及专门用于文件的类型:

/// <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);
            }
        }
    }
}

As you can see in each layer I want to follow best practices and make sure each method closes and flushes the stream for the caller and not leave things open for the sake of unit testing (I know I could probably change the code to not close the streams, but I don't think that is appropriate). 正如您在每一层中看到的那样,我想遵循最佳实践,并确保每种方法都为调用者关闭和刷新流,并且为了进行单元测试而不将其打开(我知道我可以更改代码以不关闭流,但我认为这不合适)。

So, now, using the ideas I've found on the forums to not have anything tied specifically to file streams to help with unit testing, I'm still running into problems finding the best way to unit test this. 因此,现在,使用在论坛上发现的想法没有与文件流相关的任何东西来帮助进行单元测试,我仍然遇到寻找最佳的单元测试方法的麻烦。 Here is the unit test I am trying to write: 这是我要编写的单元测试:

[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();
            }
        }
    }
}

The stream is closed no matter what I do in the serializer.Save() call since that method uses a disposable which closes the stream (as I believe it should to prevent leaks). 无论我在serializer.Save()调用中做什么,流都是关闭的,因为该方法使用了一次性的方法来关闭流(因为我认为这样做应该防止泄漏)。 The problem is, I can no longer unit test the stream in any way to test whether any of this works. 问题是,我无法再以任何方式对流进行单元测试以测试其中任何一项是否有效。 I get exceptions thrown saying you cannot access closed streams anymore, which makes sense. 我抛出异常,说您不能再访问封闭的流,这是有道理的。 But how can I test the contents of my stream in any meaningful way? 但是,如何以任何有意义的方式测试流的内容?

I found GetBuffer on the MemoryStream which allows me to convert the raw buffer into a string and I can unit test the actual JSON blob however I want... here is what I wrote: 我在MemoryStream上找到了GetBuffer,它允许我将原始缓冲区转换为字符串,但是我可以对实际的JSON Blob进行单元测试……这是我写的:

    [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]);
        }
    }

My hopes are someone will find this useful! 我希望有人会发现这个有用!

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM