.NET 单元测试(二):入门

基于状态测试

  在上一篇文章中,咱们举了一个带返回值的例子,那么无返回值的状况下又该怎样写单元测试呢?html

有以下代码:web

public IList<string> Names = new List<string>();
  
public void Reset()
{
    Names.Clear();
}
复制代码

咱们发现,Reset方法内部执行的是Names列表的清空操做,这个操做能够抽象成对被测试类状态的更改,要验证状态更改是否符合预期,咱们只须要验证更改先后是否符合预期便可。在这里,只须要测试Reset方法是否按照咱们预期的把Names清空便可。以下:bash

/// <summary>
/// 条件:Names不为空
/// 预期:清空Names
/// </summary>
[TestMethod()]
public void ResetTest_NamesNotEmpty_NamesEmpty()
{
   //Arrange
   var document = new Document();
   document.Names.Add("name0");
   document.Names.Add("name1");

   //Action
   document.Reset();

   //Assert
   Assert.AreEqual(document.Names.Count, 0);
}
复制代码

依赖外部对象的测试

  单元测试须要可以快速独立运行,隔离掉对外部的依赖是很是必要的,好比文件系统、硬件数据、web服务等。ide

以下代码:函数

///<summary>
/// 判断当前字符串是不是合法的html字符串
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
public bool IsValidHtml(string input)
{
    var textService = new TextService();
 
    return textService.IsValidHtml(input);
}
复制代码

能够看到,当前方法依赖TextService来验证html,可是在运行单元测试时,TextService的状态是未知的,它甚至可能还未开发完成。所以,须要隔离掉对TextService的依赖。单元测试

而TextService是在IsValidHtml方法内部建立的,咱们没法隔离,这个时候就须要对方法进行一系列的修改,以使得它达到可测试的要求(这就是所谓的单元测试约束设计)。测试

再进一步的分析,能够发现依赖的是TextService提供的IsValidHtml()方法,而并不是TextService这个对象,这就好说了,让IsValidHtml()依赖能够提供html验证的接口,咱们就能够不用依赖TextService这个对象了,咱们抽取接口:优化

public interface ITextService
{
    bool IsValidHtml(string input);
}
复制代码

这样咱们就能够从对具体实现的依赖解耦为对接口的依赖,所以,在测试方法中咱们能够很方便的用一个假的ITextService的实现来替代真实的TextService,由此隔离对真实外部服务的依赖。ui

这个假的ITextService的实现咱们称为 伪对象spa

以下SubTextService就是咱们的伪对象:

public class SubTextService : ITextService
{
    private bool _isValidHtml;
 
    public void SetIsValidHtml(bool value)
    {
        _isValidHtml = value;
    }
 
    public bool IsValidHtml(string input)
    {
        return _isValidHtml;
    }
}
复制代码

有了伪对象,怎么使用起来呢?

接下来介绍几种伪对象注入的方式

  • 构造函数注入

    这种方式须要被测试类提供一个带有ITextService参数的构造函数,咱们修改被测试类:

    public Document(ITextService textService)
    {
        _textService = textService;
    }
    
    /// <summary>
    /// 判断当前字符串是不是合法的html字符串
    /// </summary>
    /// <param name="input"></param>
    /// <returns></returns>
    public bool IsValidHtml(string input)
    {
        return _textService.IsValidHtml(input);
    }
    复制代码

    接下来,在测试方法中就能够将伪对象注入进去了:

    /// <summary>
    /// 条件:传入Empty的字符串
    /// 预期:返回False
    /// </summary>
    [TestMethod()]
    public void IsValidHtml_EmptyInput_ReturnFalse()
    {
        //Arrange
        var subTextService = new SubTextService();
        subTextService.SetIsValidHtml(false);
        var document = new Document(subTextService);
    
        //Action
        var result = document.IsValidHtml(string.Empty);
    
        //Assert
        Assert.IsFalse(result);
    }
    复制代码

    这种方法比较简单,被测试类的代码改动也不大。 可是,若是方法中依赖多个外部接口,须要构造函数的参数列表可能很长;或者被测试类中不一样方法依赖了不一样的外部接口,那么须要增长多个构造函数。 所以,此方法须要根据状况谨慎使用。

  • 属性注入

    这种方式指的是被测试类将外部接口的依赖设计成能够公开属性:

    public ITextService TextService { get; set; }
    复制代码

    这样在单元测试中就能够方便的将伪对象注入进去。 这种方法简单,对被测试类改动小。 可是,将TextService设计成属性,会给外部一种TextService的赋值非必需的误解,然而在咱们的设计中TextService是必须的。 所以,不推荐使用。

  • 工厂注入

    工厂注入指的是当咱们依赖的第三方接口是用工厂新建时,经过给工厂中注入伪对象来隔离对真实对象的依赖。

    public static class TextServiceFactory
    {
        private static ITextService _textService = new TextService();
    
        public static ITextService Create()
        {
            return _textService;
        }
    
        public static void SetTextService(ITextService textService)
        {
            _textService = textService;
        }
    }
    复制代码

    这种方法也比较简单,须要对工厂方法进行修改,改动量也不大。 可根据状况使用。

  • 派生类注入

    派生类注入指的是在设计的时候,把对外部的依赖对象的获取设计成能够被继承,这样伪对象就能够在不修改原来代码的状况下完成注入:

    protected virtual ITextService GetTextService()
    {
        return new TextService();
    }
    复制代码

    写单元测试的时候,只须要用伪对象继承被测试类,就能够在重写GetTextService时,注入伪对象。

    //Document为被测试类
    public class SubDocument : Document
    {
        protected override ITextService GetTextService()
        {
            return new SubTextService();
        }
    }
    复制代码

    在单元测试时,就直接使用SubDocument便可. 这种方法比较简单,并且不须要修改被测试类代码。 推荐此方法。

  写单元测试能够为咱们的代码增长一层保护,在设计程序时考虑单元测试也能够优化咱们的设计,好处多多,何乐而不为呢(●'◡'●)

2017-3-17 23:29:07
相关文章
相关标签/搜索