代码之家  ›  专栏  ›  技术社区  ›  Chris Thompson

如何设计一个应用程序,以便使用单元测试轻松地进行测试?[复制品]

  •  1
  • Chris Thompson  · 技术社区  · 15 年前

    可能重复:
    Writing “unit testable” code?

    我刚开始单元测试。今天我找到了一个关于 interfaces in c# . 这让我想到了构建可通过设计进行测试的应用程序的最佳实践。

    在我工作的主应用程序中,我很少使用接口。这使得测试变得非常困难,因为我无法将测试数据注入大多数类中,因为每个类直接依赖于其他类,从而依赖于其他类实现。

    所以我的问题是,对于一个C应用程序,您将如何设计您的类和实现,以及您将使用什么工具来确保通过为每个方法或类提供测试数据,可以独立地对方法和类进行单元测试。

    编辑:特别是,如果您查看我链接到的问题,答案将使用: iWidget w=objectfactory.getInstance();

    我从未使用过类/对象工厂,所以我很好奇它是如何工作的以及如何实现的。

    3 回复  |  直到 15 年前
        1
  •  4
  •   jrista    15 年前

    Dependency Injection 是您正在寻找的解决方案。一般的问题与您的依赖关系的创建位置有关。通常,在编写面向对象的程序时,自然的本能是直接在需要依赖项的位置使用new关键字创建依赖项。有时您可以在构造函数中创建长期依赖项。

    要使类更具单元可测试性,您需要“反转”该规范,并从外部创建依赖项(或创建工厂/提供者/上下文,该工厂/提供者/上下文可反过来被注入并用于创建其他依赖项的实例),并将这些依赖项“注入”到类中。两种最常见的注入机制要么作为构造函数的参数,要么具有setter属性。通过以这种方式将依赖项管理外部化,您可以轻松地创建这些依赖项的模拟版本并将其传入,从而允许您在完全独立于应用程序其余部分的情况下测试代码单元。

    为了支持依赖注入并使控制反转(IOC)容器的管理变得更容易,已经出现了。IOC容器是一个框架,它允许您独立于参与这些图的类来配置依赖关系图。一旦配置了依赖关系图(通常基于感兴趣的单键类),您就可以轻松地在运行时创建对象的实例,而无需担心手动创建所有必需的依赖关系。这有助于创建非常松散耦合、易于重新配置的灵活代码。一个非常好的IOC容器的例子是 Castle Windsor 这为通过依赖注入连接类提供了一个非常丰富的框架。

    依赖注入的一个非常简单的例子如下:

    interface ITaskService
    {
        void SomeOperation();
    }
    
    interface IEntityService
    {
        Entity GetEntity(object key);
        Entity Save(Entity entity);
    }
    
    class TaskService: ITaskService
    {
        public TaskService(EntityServiceFactory factory)
        {
            m_factory = factory;
        }
    
        private EntityServiceFactory m_factory; // Dependency
    
        public void SomeOperation() // Method must be concurrent, so create new IEntityService each call
        {
            IEntityService entitySvc = m_factory.GetEntityService();
            Entity entity = entitySvc.GetEntity(...);
            // Do some work with entity
            entitySvc.Save(entity);
        }
    }
    
    class EntityServiceFactory
    {
        public EntityServiceFactory(RepositoryProvider provider)
        {
            m_provider = provider;
        }
    
        private RepositoryProvider m_provider; // Dependency
    
        public virtual IEntityService GetEntityService()
        {
            var repository = m_provider.GetRepository<Entity>();
            return new EntityService(repository);
        }
    }
    
    class EntityService: IEntityService
    {
        public EntityService(IEntityRepository repository)
        {
            m_repository = repository;
        }
    
        private IEntityRepository m_repository; // Dependency
    
        public Entity GetEntity(object key)
        {
            if (key == null) throw new ArgumentNullException("key");
    
            // TODO: Check for cached entity here?
    
            Entity entity = m_repository.GetByKey(key);
            return entity;
        }
    
        public Entity Save(Entity entity)
        {
            if (entity == null) throw new ArgumentNullException(entity);
    
            if (entity.Key == null)
            {
                entity = m_repository.Insert(entity);
            }
            else
            {
                m_repository.Update(entity);
            }
    
            return entity;
        }
    }
    
    class RepositoryProvider
    {
        public virtual object GetRepository<T>()
        {
            if (typeof(T) == typeof(Entity))
                return new EntityRepository();
            else if (...)
                // ... etc.
        }
    }
    
    interface IEntityRepository
    {
        Entity GetByKey(object key);
        Entity Insert(Entity entity);
        void Update(Entity entity);
    }
    
    class EntityRepository: IEntityRepository
    {
        public Entity GetByKey(object key)
        {
            // TODO: Load up an entity from a database here
        }
    
        public Entity Insert(Entity entity)
        {
            // TODO: Insert entity into database here
        }
    
        public void Update(Entity entity)
        {
            // TODO: Update existing entity in database here
        }
    }
    
        2
  •  1
  •   Kekoa    15 年前

    Dependency Injection 是一个伟大的原则,它允许您通过定义 mock objects 在需要的地方。

    基本上,这个想法是你将任何依赖传递给一个对象,它不会创建自己的对象来操作。

        3
  •  -1
  •   Rony    15 年前

    使用模拟框架提供测试数据

    http://ayende.com/projects/rhino-mocks.aspx
    http://code.google.com/p/moq/