【问题标题】:TDD System Under Test Creation Patterns (AutoFixture)TDD 系统被测创建模式(AutoFixture)
【发布时间】:2012-12-05 21:22:53
【问题描述】:

我正在尝试使用 SUT Factory 'pattern' 创建我的 SUT。

给定 SUT 结构:

namespace MySut
{
    public class Dep1
    {
    }

    public class Dep2
    {
    }

    public class Sut
    {
        public Sut( Dep1 dep1, Dep2 dep2 )
        {
        }
    }
}

我正在使用AutoFixture,我想知道折叠以下规范和相关 SUT 工厂方法 [有价值但] 忙碌的最佳方法是什么:

namespace MySpecifications
{
    using MySut;
    public class MySpecification
    {
        public void TestCore()
        {
            // Dont care about dependencies, testing core functionality
            var sut = CreateSut();
        }

        public void TestDep1Interaction()
        {
            // Dont care about Dep2, want to observe stuff on the Dep1 dependent object
            var sut = CreateSut( new Mock<Dep1>().Object );
        }

        public void TestDep2Interaction()
        {
            // Dont care about Dep1, want to observe stuff on the Dep2 dependent object
            var sut = CreateSut( new Mock<Dep2>().Object );
        }

        private object CreateSut( )
        {
            return CreateSut( CreateDep1(), CreateDep2() );
        }

        private object CreateSut( Dep1 dep1 )
        {
            return CreateSut( dep1, CreateDep2() );
        }

        private object CreateSut( Dep2 dep2 )
        {
            return CreateSut( CreateDep1(), dep2 );
        }

        private Sut CreateSut( Dep1 dep1, Dep2 dep2 )
        {
            return new Sut( dep1, dep2 );
        }

        private static Dep1 CreateDep1()
        {
            return new Fixture().CreateAnonymous<Dep1>();
        }

        private static Dep2 CreateDep2()
        {
            return new Fixture().CreateAnonymous<Dep2>();
        }
    }
}

类似于:

    public class MyAutoFixturedSpecification
    {
        public void TestCore()
        {
            // Dont care about dependencies, testing core functionality
            var sut = CreateSut();
        }

        public void TestDep1Interaction()
        {
            // Dont care about Dep2, want to observe stuff on the Dep1 dependent object
            var sut = CreateSut( new Mock<Dep1>().Object );
        }

        public void TestDep2Interaction()
        {
            // Dont care about Dep1, want to observe stuff on the Dep2 dependent object
            var sut = CreateSut( new Mock<Dep2>().Object );
        }

        private object CreateSut( params object[] injectedNonAnonymouses )
        {
            return new Fixture(  ).Build<Sut>(  )./*??????*/;
        }
    }

或:

    public class MyAnticipatedAutoFixturedSpecification
    {
        public void TestCore()
        {
            // Dont care about dependencies, testing core functionality
            var sut = new Fixture(  ).Build<Sut>().CreateAnonymous(  );
        }

        public void TestDep1Interaction()
        {
            // Dont care about Dep2, want to observe stuff on the Dep1 dependent object
            var sut = new Fixture().Build<Sut>()/*.With( new Mock<Dep1>().Object )*/.CreateAnonymous();
        }

        public void TestDep2Interaction()
        {
            // Dont care about Dep1, want to observe stuff on the Dep2 dependent object
            var sut = new Fixture().Build<Sut>()/*.With( new Mock<Dep2>().Object )*/.CreateAnonymous();
        }
    }

即删除所有工厂垃圾,以便我的规范可以轻松应对过渡到:

namespace MySutWithNewDependency
{
    public class Dep1
    {
    }

    public class Dep2
    {
    }

    public class Dep3
    {
    }

    public class Sut
    {
        public Sut( Dep1 dep1, Dep2 dep2, Dep3 dep3 )
        {
        }
    }
}

虽然与自动模拟容器的概念有重叠,但我仍然不是在寻找金锤子 - 只是一种能够一次模拟 0 或 1 个事物并能够自定义创建依赖项的方法对象,而不必在其依赖项集更改时重新访问对 Sut 构造函数的显式调用。

(也使用 xUnit.net(SubSpec 样式)、Moq、Ninject2(虽然我不想在我的规范中使用 DI))

【问题讨论】:

    标签: tdd moq xunit autofixture


    【解决方案1】:

    AutoFixture 可以或多或少地为您完成所有这些工作,而无需额外的脚手架。我会从一个基本的 Fixture 开始,然后用它来解决 Sut:

    var fixture = new Fixture();
    var sut = fixture.CreateAnonymous<Sut>();
    

    这假设 Dep1Dep2 可以由 AutoFixture 自动创建,但正如所展示的那样,它们可以,因为它们具有默认构造函数。

    当你想覆盖一个特定的类型时,你可以像这样使用 Register 方法:

    var fixture = new Fixture();
    
    var mock = new Mock<Dep1>();
    fixture.Register(mock.Object);
    // Setup mock if necessary...
    
    var sut = fixture.CreateAnonymous<Sut>();
    

    这将导致fixture 在任何需要Dep1 时使用mock.Object,包括在调用Sut 构造函数时。这绝对符合您在面对不断变化的构造函数时保持稳健的要求,这也是 AutoFixture 如此构建的主要原因之一。

    在更现实的情况下,Dep1 和 Dep2 可能是接口,在这种情况下,您可能希望将它们注册为“默认夹具”的一部分。这当然也可能是因为最后一次调用 Register 获胜。这意味着您可以使用一些良好的默认值配置 Fixture 实例,并且仍然可以在需要时覆盖特定类型。

    我个人使用 AutoFixture 作为自动模拟容器。 This discussion 提供了 AutoFixture 1.1 API 如何实现这一点的提示,但 AutoFixture 2.0 的新内核将提供更好的可扩展性选项。当我谈到它时,我会写一篇关于这个主题的博客文章。

    附:抱歉回复晚了,但我之前没有看到你的问题。以后,请随时联系我(例如在 Twitter 上)以获得更快的回复。

    【讨论】:

    • 谢谢,马克。我曾考虑在对该问题的评论中 @ing 你作为 ping 的一种方式,但感谢您的提议!我相信我得到了您在答案中涵盖的大部分内容(您的博客文章很清楚 - 但感谢您巩固我的理解)我确实看到了 Register 但假设流利的构建器允许我内联(而不是让注册持续存在并可能“随机”影响随后的fixture 用法,一个神奇的基类或在测试环境中使用您的正常 DI 注册。我有一个两个月大的提示下载,所以它可能已经在那里了。
    • 顺便说一句,感谢无摩擦的 TDD。您在其中拥有的许多通常不成文的约定都非常有用 - 可能有助于做一篇主帖子,为它们编制索引并给它们每个句子简短的摘要。另一个问题——你真的不打算在你的 DI 书中写一个 Ninject 章节,而是有一个 Spring.NET 吗?我知道 V2 的文档不足,但肯定是你进来的地方:P(我是一个死树人,所以我还有很长的等待时间,尽管我猜 EAP 访问对我来说是一个非常好的主意,而不是随着时间的推移,有机地发现 DI 做与不做)
    • 看看讨论,听起来(或者我选择希望/假设它确实如此)您将构建一个流畅且长期存在的语法来执行 RegisterResolve 风格的钩子应该适合这种使用模式。
    • 您几乎可以将 Fixture 视为特定于测试、基于约定的 DI 容器。所有的 Register 方法最终都会调用 Customize 方法,所以这就是所有持久魔法发生的地方。虽然 Fluent API 很不错,但我仍然发现您使用它的次数越少,并且您可以越多地依赖基本约定,您的测试就越可维护。在任何情况下,一旦您调用 Build 方法,您就“离开”了 Fixture,因此在这种情况下不会保留任何内容。
    • 关于零摩擦 TDD 的帖子,你看到这个索引了吗? blog.ploeh.dk/2009/01/28/ZeroFrictionTDD.aspx
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多