当要测试的对象依赖另一个无法控制的对象(系统相关第三方服务等),这个时候我们应该如何测试?

Posted 程序员二黑.

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了当要测试的对象依赖另一个无法控制的对象(系统相关第三方服务等),这个时候我们应该如何测试?相关的知识,希望对你有一定的参考价值。

一.问题描述

判断文件是否有效的需求变更了:有效的文件扩展名存储在文件系统中,要测试的FileVerify类就依赖FileExtensionManager类,在这种场景下如何测试FileVerify类的逻辑呢?

1     public class FileVerify
 2             
 3           public bool IsValidFileName(string fileName)
 4           
 5               FileExtensionManager manager = new FileExtensionManager();
 7               return manager.IsValid(fileName);
 8           
 9      
10  
11     public class FileExtensionManager
12      
13          public bool IsValid(string fileName)
14          
15              //从文件系统中读取文件并判断
16              return true;
17          
18      

二.破除依赖的3种解决方案

方法1.对被测试类继承并重写某些行为(最简单的一种方法 无需引入新的接口和实现类)该方法在简单的同时也同时失去了对被被测试代码更多的控制空间,也就时说能做的事情是有限的

修改被测试代码,将IsValid方法定义为virtual,这样子类就可以重写该方法并决定该方法返回的结果

public class FileVerify
    
        public bool IsValidFileName(string fileName)
        
            return IsValid(fileName);
        

        public virtual bool IsValid(string fileName)
        
            FileExtensionManager manager = new FileExtensionManager();
            return manager.IsValid(fileName);
        
    

在测试类中创建FileVerify的子类TestFileVerity并修改测试类

internal class TestFileVerify :FileVerify
    
        public bool IsSupported  get; set; 

        public override bool IsValid(string fileName)
        
            return IsSupported;
        
    

   [TestFixture]
    public class FileVerifyTests
    
        [Test]
        public void IsValidFileName_NameSupportedExtension_RetureTrue()
        
            TestFileVerify fileVerify = new TestFileVerify();
            fileVerify.IsSupported = true;

            bool result = fileVerify.IsValidFileName("test.txt");

            Assert.IsTrue(result);
        
    

注意:以下方法均需在被测试项目中定义接口IExtensionManager,并在测试项目中添加一个接口的实现类FakeExtensionManager,下面为具体代码:

public interface IExtensionManager
    
        bool IsValid(string fileName);
    

    public class FileExtensionManager : IExtensionManager
    
        public bool IsValid(string fileName)
        
            //从文件系统中读取文件并判断
            return true;
        
    
internal class FakeExtensionManager : IExtensionManager
    
        public bool WillBeValid  get; set; 

        public bool IsValid(string fileName)
        
            return WillBeValid;
        
    

方法2 :继承被测试类并重写方法(与方法1相比 需引入接口与测试实现类)被测试类代码修改代码如下

public class FileVerify
    
        public bool IsValidFileName(string fileName)
        
            return GetManager().IsValid(fileName);
        

        public virtual IExtensionManager GetManager()
        
            return new FileExtensionManager();
        
    

在测试类中创建FileVerify的子类TestFileVerity并修改测试类

internal class TestFileVerify :FileVerify
    
        public TestFileVerify(IExtensionManager manager)
        
            this.manager = manager;
        

        private readonly IExtensionManager manager;

        public override IExtensionManager GetManager()
        
            return manager;
        
    

    [TestFixture]
    public class FileVerifyTests
    
        [Test]
        public void IsValidFileName_NameSupportedExtension_RetureTrue()
        
            FakeExtensionManager manager = new FakeExtensionManager();
            manager.WillBeValid = true;
            TestFileVerify fileVerify = new TestFileVerify(manager);

            bool result = fileVerify.IsValidFileName("test.txt");

            Assert.IsTrue(result);
        
    

方法3:该方法的思路是被测试类依赖于接口,不依赖于具体的实现 那么问题就转换成如何给被测试类传入具体的依赖项 对于这个思路有几个解决方案

①构造函数注入

public class FileVerify
    
        public FileVerify(IExtensionManager manager)
        
            this.manager = manager;
        

        private readonly IExtensionManager manager;

        public bool IsValidFileName(string fileName)
        
            return manager.IsValid(fileName);
        
    

使用构造函数注入需注意:

  • 在只有一个构造函数的情况下,这个类的所有使用者都必须传入依赖
  • 当这个类还需其它的依赖,例如日志服务、Web服务,那么构造函数中会加入更多的参数,会降低可读性和可维护性;解决这种情况有两种方案:①创建一个特殊类,将创建这个类所需依赖的类型作为属性,而构造函数中只有一个参数,就是这个特殊类 ②使用第三方Ioc容器来管理依赖
      
    ②属性注入
public class FileVerify
    
        public FileVerify()
        
            manager = new FileExtensionManager();
        

        public IExtensionManager Manager
        
            get => manager;
            set => manager = value;
        

        private IExtensionManager manager;

        public bool IsValidFileName(string fileName)
        
            return manager.IsValid(fileName);
        
    

使用属性注入要比使用构造函数注入比较简单,每个测试只需要设置自己需要设置的属性

③使用工厂,从工厂类中获得实例,在被测试项目中创建工厂类的代码如下:

/// <summary>
    /// 扩展管理器工厂
    /// </summary>
    internal class ExtensionManagerFactory
    
        private static IExtensionManager _manager;

        public static IExtensionManager Create()
        
            if(_manager != null)
            
                return _manager;
            
            return new FileExtensionManager();
        

        public static void SetManager(IExtensionManager manager)
        
            _manager = manager;
        
    

被测试类代码如下:在构造函数中通过工厂类创建默认的实例

public class FileVerify
    
        public FileVerify()
        
            manager = ExtensionManagerFactory.Create();
        

        private IExtensionManager manager;

        public bool IsValidFileName(string fileName)
        
            return manager.IsValid(fileName);
        
    

测试代码如下:

[TestFixture]
    public class FileVerifyTests
    
        [Test]
        public void IsValidFileName_NameSupportedExtension_RetureTrue()
        
            FakeExtensionManager manager = new FakeExtensionManager();
            manager.WillBeValid = true;
            ExtensionManagerFactory.Create();
            FileVerify fileVerify = new FileVerify();

            bool result = fileVerify.IsValidFileName("test.txt");

            Assert.IsTrue(result);
        
    

方法2和方法3比方法1相对来说比较麻烦,因为引入了新的接口,新的实现,引入了工厂方法,但是这两种方法的可控制空间比较大,例如,可以在FakeExtensionManager类中模拟异常

破除依赖的3中方法就介绍完了,如有不对之处,请指出,大家共同学习!


资源分享

下面这份资源,对于想学习【软件测试】的朋友来说应该是最全面最完整的备战仓库,希望也能帮助到你!

以上是关于当要测试的对象依赖另一个无法控制的对象(系统相关第三方服务等),这个时候我们应该如何测试?的主要内容,如果未能解决你的问题,请参考以下文章

读书笔记-单元测试艺术-使用桩对象解除依赖

2017.8.11测试理论知识 | 面向对象的集成测试和系统测试

设计模式 命令-Command

c#依赖注入和控制反转的书籍

Spring1

Spring学习第0节 -- 核心技术Ioc容器IOC理解