编辑:我决定完全放弃这个想法,因为虽然使用相同的方法来创建我的实例很好,但我放弃了其他事情并使问题复杂化。
在我的单元测试项目中,我有一个文件夹,其中基本上包含我的单元测试的所有工厂类,如下面的那个。
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>();
}
}
}
这有助于我保持单元测试的清洁并专注于测试,我只是想知道其他人如何处理这个问题以及他们在做什么来保持测试的清洁。
更新:
作为对 Adronius 的回应,我更新了我的帖子,其中包含了我旨在减少这些工厂的原型。
一个主要问题是在我的测试中使用完全相同的语法来创建实例并减少工厂类的数量。
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>();
}
}
}
}
我不知道它是否有效,这只是我为帖子提出的一个概念,以证明我的观点,我想知道人们对此有何看法?如果您有任何建议或任何事情,我会很高兴听到的。
我不喜欢重新发明轮子,所以如果你有更好的方法,我也想听听。:)