В настоящее время я пишу некоторый код, чтобы попытаться поэкспериментировать с разделением и абстрагированием двух частей наших стратегий хранения на работе.В настоящее время мы используем формат 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 (), так как этот метод использует одноразовый метод, который закрывает поток (как ясчитаю, что это должно предотвратить утечки).Проблема в том, что я больше не могу тестировать поток каким-либо образом, чтобы проверить, работает ли что-то из этого.Я получаю исключения, говорящие, что вы больше не можете получить доступ к закрытым потокам, что имеет смысл.Но как я могу проверить содержимое моего потока любым значимым способом?