在一个类的所有方法中使用Virtual关键字的后果?

我是TDD的新手,我使用Moq作为我的嘲笑框架。 我正在尝试检查我的课程中是否有方法。 该类没有实现任何接口。

 var mockFooSaverService = new Mock<FooSaverService>();
 mockFooSaverService.Verify(service => service.Save(mockNewFoo.Object));

为了做到这一点,我在这里发现我必须将Save()方法作为Virtual方法。

题:

为了使其可测试而对类中的所有方法使用Virtual关键字有什么后果?


TL; DR

根据注释,对virtual关键字的需求表明您的类层次结构过于紧密,您应该使用SOLID原则将它们与对方解耦。 由于依赖性可以通过接口抽象来模拟,所以这会让您的类层次结构更容易进行单元测试,这具有“快乐”的副作用。

更详细地说

需要使所有公共方法虚拟化以允许Moq覆盖它们通常表示关注点或类耦合气味的分离。 例如,这个场景需要虚拟方法,因为被测试的类有多个问题,需要模拟一个方法并实际调用被测系统中的另一个方法。

根据@ JonSkeet的评论,将依赖关系抽象为接口是最常见的SOLID最佳实践。 就目前而言,你的班级正在测试中(我可以称之为“Controller”?)取决于具体的FooSaverService以保存Foos。

通过应用依赖倒置原则,通过将FooSaverService的外部有用方法,属性和事件FooSaverService为接口( IFooSaverService ),可以放宽这种耦合,然后

  • FooSaverService实现IFooSaverService
  • Controller仅依赖于IFooSaverService
  • (显然,可能还有其他优化,例如,使IFooSaverService泛型,例如ISaverService<Foo>但不在此范围内)

    Re: Mock<Foo> - 需要模拟简单的数据存储类(POCO's,Entities,DTO等)是相当罕见的,因为它们通常会保留存储在其中的数据,并且可以直接在单元测试中进行推理。

    为了回答你对Virtual问题的重新论述(现在希望不那么重要):

  • 你打破了(多态)开放原则和闭合原则 - 它正在邀请其他人重写行为,而不是故意为此设计 - 可能会有意想不到的后果。
  • 根据Henk的评论,在管理虚拟方法表时将会产生很小的性能影响
  • 一个代码示例

    如果你把所有这些放在一起,你将会得到一个像这样的类层次结构:

    // Foo is assumed to be an entity / POCO
    public class Foo
    {
        public string Name { get; set; }
        public DateTime ExpiryDate { get; set; }
    }
    
    // Decouple the Saver Service dependency via an interface
    public interface IFooSaverService
    {
        void Save(Foo aFoo);
    }
    
    // Implementation
    public class FooSaverService : IFooSaverService
    {
        public void Save(Foo aFoo)
        {
            // Persist this via ORM, Web Service, or ADO etc etc.
        }
        // Other non public methods here are implementation detail and not relevant to consumers
    }
    
    // Class consuming the FooSaverService
    public class FooController
    {
        private readonly IFooSaverService _fooSaverService;
    
        // You'll typically use dependency injection here to provide the dependency
        public FooController(IFooSaverService fooSaverService)
        {
            _fooSaverService = fooSaverService;
        }
    
        public void PersistTheFoo(Foo fooToBeSaved)
        {
            if (fooToBeSaved == null) throw new ArgumentNullException("fooToBeSaved");
            if (fooToBeSaved.ExpiryDate.Year > 2015)
            {
                _fooSaverService.Save(fooToBeSaved);
            }
        }
    }
    

    然后你将能够测试你的类,它具有IFooSaverService依赖关系,如下所示:

    [TestFixture]
    public class FooControllerTests
    {
        [Test]
        public void PersistingNullFooMustThrow()
        {
            var systemUnderTest = new FooController(new Mock<IFooSaverService>().Object);
            Assert.Throws<ArgumentNullException>(() => systemUnderTest.PersistTheFoo(null));
        }
    
        [Test]
        public void EnsureOldFoosAreNotSaved()
        {
            var mockFooSaver = new Mock<IFooSaverService>();
            var systemUnderTest = new FooController(mockFooSaver.Object);
            systemUnderTest.PersistTheFoo(new Foo{Name = "Old Foo", ExpiryDate = new DateTime(1999,1,1)});
            mockFooSaver.Verify(m => m.Save(It.IsAny<Foo>()), Times.Never);
        }
    
        [Test]
        public void EnsureNewFoosAreSaved()
        {
            var mockFooSaver = new Mock<IFooSaverService>();
            var systemUnderTest = new FooController(mockFooSaver.Object);
            systemUnderTest.PersistTheFoo(new Foo { Name = "New Foo", ExpiryDate = new DateTime(2038, 1, 1) });
            mockFooSaver.Verify(m => m.Save(It.IsAny<Foo>()), Times.Once);
        }
    }
    

    TL; DR; 另一个好的答案是 - 使类可扩展,并提供虚拟方法(即扩展它们的可能性)是该类的“特征”。 此功能需要作为任何其他功能来支持和测试。

    Eric Lippert的博客可以阅读更多更好的解释。

    链接地址: http://www.djcxy.com/p/38127.html

    上一篇: Consequences of using Virtual keyword on all methods in a class?

    下一篇: Why can't a virtual function override an abstract function?