編集:同じ方法を使用してインスタンスを作成するのは良いことですが、他のことを諦めて問題を複雑にしているため、アイデアを完全に捨てることにしました。
私の単体テストプロジェクトには、基本的に、以下のような単体テストのすべてのファクトリクラスを含むフォルダがあります。
public static class PackageFileInfoFactory
{
private const string FILE_NAME = "testfile.temp";
public static PackageFileInfo CreateFullTrustInstance()
{
var mockedFileInfo = CreateMockedInstance(AspNetHostingPermissionLevel.Unrestricted);
return mockedFileInfo.Object;
}
public static PackageFileInfo CreateMediumTrustInstance()
{
var mockedFileInfo = CreateMockedInstance(AspNetHostingPermissionLevel.Medium);
return mockedFileInfo.Object;
}
private static Mock<PackageFileInfo> CreateMockedInstance(AspNetHostingPermissionLevel trustLevel)
{
var mockedFileInfo = new Mock<PackageFileInfo>(FILE_NAME);
mockedFileInfo.Protected().SetupGet<AspNetHostingPermissionLevel>("TrustLevel").Returns(() => trustLevel);
mockedFileInfo.Protected().Setup<string>("CopyTo", ItExpr.IsAny<string>()).Returns<string>(destFileName => "Some Unknown Path");
return mockedFileInfo;
}
}
これが私のユニットテストのサンプルです。
public class PackageFileInfoTest
{
public class Copy
{
[Fact]
public void Should_throw_exception_in_medium_trust_when_probing_directory_does_not_exist()
{
// Arrange
var fileInfo = PackageFileInfoFactory.CreateMediumTrustInstance();
fileInfo.ProbingDirectory = "SomeDirectory";
// Act
Action act = () => fileInfo.Copy();
// Assert
act.ShouldThrow<InvalidOperationException>();
}
[Fact]
public void Should_throw_exception_in_full_trust_when_probing_directory_does_not_exist()
{
// Arrange
var fileInfo = PackageFileInfoFactory.CreateFullTrustInstance();
fileInfo.ProbingDirectory = "SomeDirectory";
// Act
Action act = () => fileInfo.Copy();
// Assert
act.ShouldThrow<InvalidOperationException>();
}
[Fact]
public void Should_throw_exception_when_probing_directory_is_null_or_empty()
{
// Arrange
var fileInfo = PackageFileInfoFactory.CreateFullTrustInstance();
// Act
Action act = () => fileInfo.Copy();
// Assert
act.ShouldThrow<InvalidOperationException>();
}
}
}
これは、単体テストをクリーンに保ち、テストに集中するのに役立ちます。他の人がこれをどのように処理し、テストをクリーンに保つために何をしているのか疑問に思っています。
アップデート:
アドロニウスに応えて、私はこれらの工場を減らすことを目指しているもののプロトタイプで私の投稿を更新しました。
主な懸念事項は、インスタンスを作成し、ファクトリクラスの数を減らすために、テスト全体でまったく同じ構文を使用することです。
namespace EasyFront.Framework.Factories
{
using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using EasyFront.Framework.Diagnostics.Contracts;
/// <summary>
/// Provides a container to objects that enable you to setup them and resolve instances by type.
/// </summary>
/// <remarks>
/// Eyal Shilony, 20/07/2012.
/// </remarks>
public class ObjectContainer
{
private readonly Dictionary<string, object> _registeredTypes;
public ObjectContainer()
{
_registeredTypes = new Dictionary<string, object>();
}
public TResult Resolve<TResult>()
{
string keyAsString = typeof(TResult).FullName;
return Resolve<TResult>(keyAsString);
}
public void AddDelegate<TResult>(Func<TResult> func)
{
Contract.Requires(func != null);
Add(typeof(TResult).FullName, func);
}
protected virtual TResult Resolve<TResult>(string key)
{
Contract.Requires(!string.IsNullOrEmpty(key));
if (ContainsKey(key))
{
Func<TResult> func = GetValue<Func<TResult>>(key);
Assert.NotNull(func);
return func();
}
ThrowWheNotFound<TResult>();
return default(TResult);
}
protected void Add<T>(string key, T value) where T : class
{
Contract.Requires(!string.IsNullOrEmpty(key));
_registeredTypes.Add(key, value);
}
protected bool ContainsKey(string key)
{
Contract.Requires(!string.IsNullOrEmpty(key));
return _registeredTypes.ContainsKey(key);
}
protected T GetValue<T>(string key) where T : class
{
Contract.Requires(!string.IsNullOrEmpty(key));
return _registeredTypes[key] as T;
}
protected void ThrowWheNotFound<TResult>()
{
throw new InvalidOperationException(string.Format("The type '{0}' was not found in type '{1}'.", typeof(TResult).FullName, GetType().ReflectedType.FullName));
}
[ContractInvariantMethod]
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Required for code contracts.")]
private void ObjectInvariant()
{
Contract.Invariant(_registeredTypes != null);
}
}
}
次に、これを拡張して、このようなパラメーターを取得できます。
namespace EasyFront.Framework.Factories
{
using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using EasyFront.Framework.Diagnostics.Contracts;
public class ObjectContainer<T> : ObjectContainer
{
public TResult Resolve<TResult>(T key = default(T))
{
string keyAsString = EqualityComparer<T>.Default.Equals(key, default(T)) ? typeof(TResult).FullName : GetKey(key);
Assert.NotNullOrEmpty(keyAsString);
return Resolve<TResult>(keyAsString);
}
public void AddDelegate<TReturn>(T key, Func<T, TReturn> func)
{
Contract.Requires(func != null);
Add(GetKey(key), func);
}
protected override TResult Resolve<TResult>(string key)
{
if (ContainsKey(key))
{
Func<TResult> func = GetValue<Func<TResult>>(key);
Assert.NotNull(func);
return func();
}
throw new InvalidOperationException(string.Format("The type '{0}' was not setup for type '{1}'.", typeof(TResult).FullName, GetType().ReflectedType.FullName));
}
/// <summary> Gets the full name of the type and the hash code as the key. </summary>
/// <remarks> Eyal Shilony, 20/07/2012. </remarks>
/// <param name="value"> The value to use to get key. </param>
/// <returns> The full name of the type and the hash code as the key. </returns>
private static string GetKey(T value)
{
Contract.Ensures(!string.IsNullOrEmpty(Contract.Result<string>()));
string key = value.GetType().FullName + "#" + value.ToString().GetHashCode();
Assert.NotNullOrEmpty(key);
return key;
}
}
}
実装は次のようになります。
namespace EasyFront.Tests.Factories
{
using System.Web;
using EasyFront.Framework.Factories;
using EasyFront.Framework.Web.Hosting.Packages;
using Moq;
using Moq.Protected;
public class PackageFileInfoFactory : IObjectFactory<AspNetHostingPermissionLevel>
{
private const string FILE_NAME = "testfile.temp";
private readonly ObjectContainer<AspNetHostingPermissionLevel> _container;
public PackageFileInfoFactory()
{
_container = new ObjectContainer<AspNetHostingPermissionLevel>();
_container.AddDelegate(AspNetHostingPermissionLevel.Unrestricted, value =>
{
var mockedFileInfo = CreateMockedInstance(value);
return mockedFileInfo.Object;
});
_container.AddDelegate(AspNetHostingPermissionLevel.Medium, value =>
{
var mockedFileInfo = CreateMockedInstance(value);
return mockedFileInfo.Object;
});
}
public TResult CreateInstance<TResult>(AspNetHostingPermissionLevel first)
{
return _container.Resolve<TResult>(first);
}
private static Mock<PackageFileInfo> CreateMockedInstance(AspNetHostingPermissionLevel trustLevel)
{
var mockedFileInfo = new Mock<PackageFileInfo>(FILE_NAME);
mockedFileInfo.Protected().SetupGet<AspNetHostingPermissionLevel>("TrustLevel").Returns(() => trustLevel);
mockedFileInfo.Protected().Setup<string>("CopyTo", ItExpr.IsAny<string>()).Returns<string>(destFileName => "Some Unknown Path");
return mockedFileInfo;
}
}
}
そしてついにこのように使えるようになりました。
namespace EasyFront.Framework.Web.Hosting.Packages
{
using System;
using System.Web;
using EasyFront.Tests.Factories;
using FluentAssertions;
using global::Xunit;
public class PackageFileInfoTest
{
public class Copy
{
private readonly PackageFileInfoFactory _factory;
public Copy()
{
_factory = new PackageFileInfoFactory();
}
[Fact]
public void Should_throw_exception_in_medium_trust_when_probing_directory_does_not_exist()
{
// Arrange
var fileInfo = _factory.CreateInstance<PackageFileInfo>(AspNetHostingPermissionLevel.Medium);
fileInfo.ProbingDirectory = "SomeDirectory";
// Act
Action act = () => fileInfo.Copy();
// Assert
act.ShouldThrow<InvalidOperationException>();
}
[Fact]
public void Should_throw_exception_in_full_trust_when_probing_directory_does_not_exist()
{
// Arrange
var fileInfo = _factory.CreateInstance<PackageFileInfo>(AspNetHostingPermissionLevel.Unrestricted);
fileInfo.ProbingDirectory = "SomeDirectory";
// Act
Action act = () => fileInfo.Copy();
// Assert
act.ShouldThrow<InvalidOperationException>();
}
[Fact]
public void Should_throw_exception_when_probing_directory_is_null_or_empty()
{
// Arrange
var fileInfo = _factory.CreateInstance<PackageFileInfo>(AspNetHostingPermissionLevel.Unrestricted);
// Act
Action act = () => fileInfo.Copy();
// Assert
act.ShouldThrow<InvalidOperationException>();
}
}
}
}
それが機能しているかどうかはわかりませんが、それは私の主張を示すために投稿のために作成した概念にすぎません。人々はそれについてどう思うのでしょうか。何か提案や何かがあれば、私はそれについて聞いてうれしいです。
私は車輪の再発明が好きではないので、あなたがより良いアプローチを持っているなら、私もそれについて聞きたいです。:)