1

编辑:我决定完全放弃这个想法,因为虽然使用相同的方法来创建我的实例很好,但我放弃了其他事情并使问题复杂化。

在我的单元测试项目中,我有一个文件夹,其中基本上包含我的单元测试的所有工厂类,如下面的那个。

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

我不知道它是否有效,这只是我为帖子提出的一个概念,以证明我的观点,我想知道人们对此有何看法?如果您有任何建议或任何事情,我会很高兴听到的。

我不喜欢重新发明轮子,所以如果你有更好的方法,我也想听听。:)

4

2 回答 2

0

我在单元测试中使用了几乎相同的方法。

如果我在一个测试夹具类中有重复,我将大部分创建(而不是初始化)模拟(双打)放入 setUp 方法。

但是几个测试夹具类之间仍然存在重复。因此,对于那些重复,我使用看起来与您的相似的“ TestDoubleFactory ”静态类,除了我没有创建实例的方法,但我总是只创建模拟对象,因此我可以在测试中进一步修改(设置)它们。

于 2012-07-20T18:45:36.670 回答
0

我无法在 2 分钟内理解您更新中的 test-factory-class-framework,因此我认为它不会使维护测试变得更容易。

但是,我喜欢您拥有集中式测试数据生成器的概念。

在我看来,在大多数情况下,每种类型的一个测试数据工厂方法就足够了。此方法定义类型的标准测试数据。

实际测试将差异分配给标准测试数据。

简单的例子:

public class PackageFileInfoTest
{
    public class Copy
    {
        [Fact]
        public void Should_throw_exception_when_probing_directory_does_not_exist()
        {
            // Arrange
            var fileInfo = TestData.CreatePackageFileInfo();
            fileInfo.ProbingDirectory = "/Some/Directory/That/Does/Not/Exist";
            // Act
            Action act = () => fileInfo.Copy();

            // Assert
            act.ShouldThrow<InvalidOperationException>();
        }

注意:“不存在的目录测试”应该独立于“权限级别”。因此,工厂的标准许可应该足够了。

当涉及多个类型时,每个类型一个工厂方法的更复杂示例:

        [Fact]
        public void Should_throw_exception_in_low_trust_when_writing_to_protected_directory()
        {
            // Arrange
            var protectedDirectory = TestData.CreateDirectory();
            protectedDirectory.MinimalPermissionRequired = AspNetHostingPermissionLevel.Full;

            var currentUser= TestData.CreateUser();
            currentUser.TrustLevel = AspNetHostingPermissionLevel.Low;

            // Act
            Action act = () => FileUploadService.CopyTo(protectedDirectory);

            ....
    }
}

在 Dotnet 世界中, nbuilder可以帮助您填充测试数据数组。

于 2012-07-21T06:00:33.987 回答