返回首页

Introductionnbsp;
您可能已经听说过拓展署最近有多大等,但大多数的时间,我们对项目有一个大的代码库,并​​我们是不允许剧变的工作。工具{A1}
的情况下我们面临的重构遗留代码时的情景,我的经验:我们是免费申请的变化,我们希望,世界是完美的。 :)我们可以申请,但只有与单元测试的目的,我们想要的变化,生产代码应该像往常一样工作。我们不允许改变基础设施以外的任何部分的代码,但记住的东西必须一如既往地工作,允许没有重大的变动。
让我们在每一个这些场景的阐述,我们如何履行我们的变化,使传统的代码更容易测试。具体的例子

    public static class DataBaseHelper

    {

        public static void Persist(object instance)

        {

            Console.WriteLine("Persisting object " + instance);

        }

    }


{C}


正如你可以看到,坚持从DataBaseHelper什么也不做的方法,但我们会承担我们一些与数据库的硬依赖也许一个SqlConnection的一个SqlCommand的,等等。


现在,这段代码什么的那么糟糕吗?我们大多数人都见过,做maintainednbsp;多年这样的东西,如果你这个罚款,勇往直前,我不会在这里告诉你什么是错的,什么是完美的,是没有完美的解决方案。不过,我想向你展示的另一种方式,一种更好的方式,在我看来


我问什么是坏,一段代码,我没有回答,还... ...让我们来看看:{BR }

你能真正确保在以下天/周/月/年的代码将如预期?

,如果你在未来发现系统中的一个错误,你将如何丢弃问题的原因是不相关的这种方法?和检查这一事实时,你需要有你的调试器/ Visual Studio实例运行起来?


我想答案不那么令人愉快的所有,但不要担心,这篇文章的想法是,以帮助您找到一个合理的路径,使用TDD开发软件时。

应该我们的测试覆盖


好了,有几件事情,我们必须确保在我们的测试:


该产品通过服务方法进入其与真值的Enabled属性,和False值必须退出。

服务要坚持更新的产品实例。


现在让我们回到场景小节和每一个这些点提供一种解决方法。

我们免费申请的变化,我们希望,世界是完美的。 :)


这使我们能够使用一个奇妙的图案,我对{A2}


使用该谈论,我们可以做出如下介绍了以下测试:
    public class Scenario1Tests

    {

        #region Mocks

        private class NoOpProductRepository : IProductRepository

        {

            public void Persist(Product product)

            {

                // no-op

            }

        }



        private class VerifiableProductRepository : IProductRepository

        {

            public event Action<Product> ProductPersisted;

            public void Persist(Product product)

            {

                var productPersistedEvent = ProductPersisted;

                if (productPersistedEvent != null)

                    productPersistedEvent(product);

            }



        }



        #endregion





        [Test]

        public void when_disabling_a_product_its_enabled_property_is_set_to_false()

        {

            // Arrange

            var product = new Product

                              {

                                  ProductId = 1,

                                  ProductName = "Car",

                                  Enabled = true

                              };

            // here we should use a mocking framework like Moq or Rhino.Mocks.

            IProductRepository mockedRepository = new NoOpProductRepository();



            var service = new ProductService(mockedRepository);



            // Act

            service.DisableProduct(product);



            // Assert

            Assert.AreEqual(false, product.Enabled);

        }



        [Test]

        public void when_disabling_a_product_it_persist_the_changes_using_the_product_repository()

        {

            // Arrange

            var product = new Product

            {

                ProductId = 1,

                ProductName = "Car",

                Enabled = true

            };

            Product productPassedToPersistMethod = null;

            var valueOfProductEnabledPropertyPassedToPersistMethod = false;

            // here we should use a mocking framework like Moq or Rhino.Mocks.

            var mockedRepository = new VerifiableProductRepository();

            mockedRepository.ProductPersisted +=

                instance =>

                    {

                        productPassedToPersistMethod = instance;

                        valueOfProductEnabledPropertyPassedToPersistMethod = instance.Enabled;

                    };



            var service = new ProductService(mockedRepository);



            // Act

            service.DisableProduct(product);



            // Assert

            Assert.IsNotNull(productPassedToPersistMethod);

            Assert.AreSame(product, productPassedToPersistMethod);

            Assert.AreEqual(false, valueOfProductEnabledPropertyPassedToPersistMethod);

        }





    }


正如你可以看到,这些都是相当巨大的变化,以便再次使用我们的ProductService现在我们要提供一个IProductRepository实施,因为它的构造要求之一。静态辅助类来访问数据库的使用已经移到我们的ProductRepository这是很可能是在运行时使用的具体实施。这是像StructureMap的IOC / DI工具的使用真正的亮点,因为它将帮助我们配置这样的顾虑(即连接ProductRepository IProductRepository)和透明其usage.nbsp;
现在我们获得了什么做这?好了,现在我们可以测试嘲讽它的依赖关系,并确保一切都如预期的在我们的荣幸ProductService.DisableProduct方法。如果将来有人惹尝试这种方法的实施,或如果我们引入我们的系统的变化,我们将有这两个单元测试,以弥补我们的背上,他们将让我们在晚上睡觉紧。我认为你能想出什么测试只是在代码,这是相当简单,它不超过10分钟来创建它们。我们希望,我们可以申请,但只有与单元测试的目的的变化,生产代码应该像往常一样工作。


在这里,我们可以使用被称为穷人的依赖注入的东西,你可以读取{A5},并让我告诉你,我同意麦Bogard让我们在这篇文章中的思想。我真的觉得,亲爱的开发者,我们应该努力的所有选项头号,推动很难让管理变革的基础设施,使用适当的架构和DI / IOC工具。但是,世界并不完美,管理或我们的工作环境并不总是太客气了接受改变。


所以我们做什么?,我们采取的第一种方法的代码,并修改ProductService类,并引入一个无参数的构造函数调用需要在一个IProductRepository实例中的构造和传递给它的ProductRepository类的一个实例,因此,删除一个DI / IOC工具需要建立在我们方面的实例。


下面是代码说明什么,我是在上段喃喃自语:

    public class ProductService

    {

        private readonly IProductRepository _productRepository;



        public ProductService()

            : this(new ProductRepository())

        {



        }



        public ProductService(IProductRepository productRepository)

        {

            _productRepository = productRepository;

        }



        public void DisableProduct(Product product)

        {

            product.Enabled = false;

            _productRepository.Persist(product);

        }

    }




测试和所有其他的东西,我们保持同样的,我们仍然有一个可测试的ProductService类。我们不允许改变基础设施以外的任何部分的代码,但记住的东西必须一如既往地工作,允许没有重大的变动。


这意味着,没有接口,没有库,没有什么,因此,没有新的概念,允许将主要代码库介绍,你应该保持你的修改尽可能低的
,我必须承认,没有让我们多的选择,挑选... ...但是,即使在这种僵化环境中,我们可以用简单的方法更好。
我们将要做的是把我们的帮手,并使其具有足够的灵活性,让我们检查工作预计在未来的ProductService。当然,这种技术是不是我的,此刻我能找到的资源,我第一次读它,所以,如果有人有你一个链接,是我的客人。
    public static class DataBaseHelper

    {

        private static Action<object> _persistenceDelegate;



        static DataBaseHelper()

        {

            _persistenceDelegate = instance => Console.WriteLine("Persisting object " + instance);

        }



        public static void SetPersistenceDelegate(Action<object> persistenceDelegate)

        {

            _persistenceDelegate = persistenceDelegate;

        }

        public static Action<object> GetPersistenceDelegate()

        {

            return _persistenceDelegate;

        }



        public static void Persist(object instance)

        {

            _persistenceDelegate(instance);

        }

    }






可以看到,现在我们的静态辅助persist方法使用的SetPersistenceDelegate方法方式,可以交换的逻辑,GetPersistenceDelegate是有只是为了满足单元测试的需要,我们想对每个测试复位代表的价值(唐"不用担心,我将展示这个版本)。另一个要注意的是,这两种方法都是公开的,好了,我想我们可以让他们内部只允许单元测试组件使用它们,是向读者做太起见,我觉得这个演示,它是确定的(提示:{A6})。
现在,让我们的看到如何我们的单元测试看起来像:
    public class Scenario3Tests

    {

        #region Init, Before and After each test

        private Action<object> _originalDelegate;



        [TestFixtureSetUp]

        protected  void Init()

        {

            _originalDelegate = DataBaseHelper.GetPersistenceDelegate();

        }



        [SetUp]

        protected void BeforeEachTest()

        {

            DataBaseHelper.SetPersistenceDelegate(_originalDelegate);

        }



        [TearDown]

        protected void AfterEachTest()

        {

            DataBaseHelper.SetPersistenceDelegate(_originalDelegate);

        }



        #endregion



        [Test]

        public void when_disabling_a_product_its_enabled_property_is_set_to_false()

        {

            // Arrange

            var product = new Product

                              {

                                  ProductId = 1,

                                  ProductName = "Car",

                                  Enabled = true

                              };

            var service = new ProductService();



            // Act

            service.DisableProduct(product);



            DataBaseHelper.SetPersistenceDelegate(instance =>

                                                      {

                                                          // no-op! 

                                                      });

            // Assert

            Assert.AreEqual(false, product.Enabled);

        }



        [Test]

        public void when_disabling_a_product_it_persist_the_changes_using_the_product_repository()

        {

            // Arrange

            var product = new Product

            {

                ProductId = 1,

                ProductName = "Car",

                Enabled = true

            };

            Product productPassedToPersistMethod = null;

            var valueOfProductEnabledPropertyPassedToPersistMethod = false;

            DataBaseHelper.SetPersistenceDelegate(

                instance =>

                    {

                        productPassedToPersistMethod = (Product) instance;

                        valueOfProductEnabledPropertyPassedToPersistMethod = productPassedToPersistMethod.Enabled;

                    });



            var service = new ProductService();



            // Act

            service.DisableProduct(product);



            // Assert

            Assert.IsNotNull(productPassedToPersistMethod);

            Assert.AreSame(product, productPassedToPersistMethod);

            Assert.AreEqual(false, valueOfProductEnabledPropertyPassedToPersistMethod);

        }

    }





正如你可以看到,在测试的基本想法仍然相同,
,而不是用嘲笑库,我们使用的是定制的代表,并更新每个测试运行,以适应测试需求的帮手委托。终曲

好了,我希望这篇文章给亲爱的读者,您是如何的想法单元测试遗留代码。如果我做了一些愚蠢的声明,请与我承担,如果是的话,请让我知道我可以学到一些技巧;)


另外,我不太很清楚如何解释应使用一个DI / IOC工具来帮助我们解决在运行时依赖,我会尝试另一篇文章来解释这一,如果你不能等待在那里可以学到,有很多文章中的净这个问题

您可以下载示例代码{A7}

回答

评论会员: 时间:2