本文主要介绍Visual Studio(2012+)单元测试框架的一些技巧:

  1. 如何模拟类的静态构造函数
  2. 如何测试某方法被调用过
  3. 如何测试某方法执行的次数
  4. 并行编程测试注意事项

一、如何模拟类的静态构造函数

1.1 被测代码

namespace BlogDemo.UTDemo.Tricks
{
public class StaticConstructorExample
{
static StaticConstructorExample()
{
//step1:read data from web service
//step2:save data into database
} public int DoSomething()
{
return ;
}
}
}

上面的类有一个静态构造函数,里面做两件事情:

  1. 从某webservice读取数据
  2. 将读取到的数据保存到数据库。

以上两件事情都是对外界环境的严重依赖

我们需要测试的方法是DoSomething,需要测试这个类,就需要实例化StaticConstructorExample这个类,实例化这个类之前,其静态构造函数将会被自动执行:这个点就是问题。

  1. 静态构造函数依赖外部环境,这个外部环境有可能在某一天就挂了
  2. 如果外部环境挂了,静态构造函数就会报错,那我们的单元测试就无法按照预期进行运行
  3. DoSOmething本身不直接依赖外部环境,但是通过静态构造函数间接依赖了外部环境,这违反了单元测试repeatable原则(当外部环境挂了,我们的测试就无法运行)

解决办法依然是Shim,通过ms.test的Shim可以模拟一个类的静态构造函数,从而改变静态构造函数的行为,消除外部依赖。

1.2 测试代码

 [TestClass]
public class StaticConstructorExampleTests
{
[TestMethod]
public void DoSomethingTest()
{
using (ShimsContext.Create())
{
var isStaticConstructorExecuted = false;
ShimStaticConstructorExample.StaticConstructor = () =>
{
//step1:mock web service
//step2:mock database
isStaticConstructorExecuted = true;
};
var example = new StaticConstructorExample();
var data = example.DoSomething();
Assert.AreEqual(, data);
Assert.IsTrue(isStaticConstructorExecuted);
} }
}

上面的代码是通过ShimClassName.StaticConstructor来实现对静态构造函数进行模拟的。

二、如何测试某方法被调用过

2.1 被测代码

基础代码,在DemoClass中进行调用

    public interface IGoodActionHandler
{
void Action();
} public interface IBadActionHandler
{
void Action();
} public class GoodHandler : IGoodActionHandler
{
public void Action()
{
throw new NotImplementedException();
}
} public class BadHandler : IBadActionHandler
{
public void Action()
{
throw new NotImplementedException();
}
}

要被测试的类:

  public class DemoClass
{
IGoodActionHandler goodHandler;
IBadActionHandler badHandler;
public DemoClass()
{
this.goodHandler = new GoodHandler();
this.badHandler = new BadHandler();
}
public void DoSomething(int type)
{
if (type == )
{
DoSomethingGood();
}
else
{
DoSomethingBad();
}
} private void DoSomethingGood()
{
this.goodHandler.Action();
} private void DoSomethingBad()
{
this.badHandler.Action();
}
}

要测试上面的DoSomething方法,上面DoSomethingGood和DoSomethingBad都依赖自接口,在测试的时候都可以进行mock(stub)。在侧测DoSomething方法是只需要验证当Type为1时执行了goodHandler的action,否则执行badHandler的Action。

这也是单元测试的一个关键点:关注单元。这里不关注goodhandler 和badhandler的内部逻辑(这两个handler的内部逻辑可以单独测试,属于另外的单元),这里只关注是否按照逻辑路由到了正确的handler。

2.2测试代码

  [TestClass]
public class DemoClassTests
{
[TestMethod]
public void DoSomething_DoSomethingGood_Tests()
{
var goodHandlerExecuted = false;
StubIGoodActionHandler goodHandler = new StubIGoodActionHandler()
{
Action = () =>
{
goodHandlerExecuted = true;//如果执行了goodhandler则置为true
}
};
var badHandlerExecuted = false;
StubIBadActionHandler badHandler = new StubIBadActionHandler()
{
Action = () =>
{
badHandlerExecuted = true;//如果执行了badhandler则置为true
}
};
var demoClass=new DemoClass();
demoClass.BadHandler=badHandler;//注入badhandler
demoClass.GoodHandler=goodHandler;//注入goodhandler
demoClass.DoSomething();
Assert.IsTrue(goodHandlerExecuted);//执行了goodhandler
Assert.IsFalse(badHandlerExecuted);//没有执行badhandler
}
}

上面使用了两个技术

  1. 面向接口编程,使用Stub技术,动态注入Stub(打桩),在测试的时候对对象进行了模拟
  2. 改变了Stub对象的行为,本文只是通过在Stub对象的内部设置标志位的值来表示是否执行了这一个步骤。

上面的标志位的方法Shim技术照样使用,静态构造函数的模拟就是通过Shim后在构造函数内部修改标识位来验证构造函数被执行的。接下来介绍的测试执行次数也是通过shim后修改标识位来实现的

三、如何测试某方法执行的次数

3.1 被测代码

  public class DemoClass
{
private LoopHandler handler;
public DemoClass()
{
handler = new LoopHandler();
}
public void DoSomething(int times)
{
for (var i = ; i < times; i++)
{
handler.Do();
}
}
} public class LoopHandler
{
public void Do() { }
}

现在要验证DoSomething是否执行了times次Do方法。

3.2 测试代码

 [TestMethod]
public void DoSomething_RunTimes_Test()
{
using(ShimsContext.Create())
{
var times = ;
ShimLoopHandler.AllInstances.Do = (@this) =>
{
times++;//每进入一次加一次
};
var givenTimes = ;
new DemoClass().DoSomething(givenTimes);
Assert.AreEqual(times, givenTimes);
}
}

思想和上面Stub是一样,在Shim模拟的方法内部进行计数器相加。

3.3 并行编程测试注意事项

这里有一点需要注意一下,因为int的++不是线程安全的,如果把DemoClass的循环修改成多个线程并行执行的话测试代码需要做相应的调整。,对times++进行lock。

 public void DoSomething(int times)
{
Parallel.For(, times, (item) =>//并行执行
{
handler.Do();
});
} [TestMethod]
public void DoSomething_RunTimes_Test()
{
using(ShimsContext.Create())
{
var times = ;
ShimLoopHandler.AllInstances.Do = (@this) =>
{
lock (this)//lock线程安全
{
times++;//每进入一次加一次
}
};
var givenTimes = ;
new DemoClass().DoSomething(givenTimes);
Assert.AreEqual(times, givenTimes);
}
}

上面的代码当Times不是特别大的时候一般不加lock也是可以的,不是特别大的时候一般不会产生线程安全问题。但是当times比较大的时候不加lock就会出问题。

当times是100000时去掉lock。我运行就出现了问题:

测试就是为了严谨,如果有上面并行的问题,测试的时候还是加上lock比较好。

使用Visual Studio进行单元测试-Part5的更多相关文章

  1. 【MVC 4】4.MVC 基本工具(Visual Studio 的单元测试、使用Moq)

     作者:[美]Adam Freeman      来源:<精通ASP.NET MVC 4> 3.Visual Studio 的单元测试 有很多.NET单元测试包,其中很多是开源和免费的.本 ...

  2. [转]Visual Studio 2010 单元测试目录

    Visual Studio 2010 单元测试共分七个部分: 普通单元测试.顺序单元测试.压力测试,Generic测试.数据库测试.UI界面测试和Web性能测试. 这个系列的博客实例程序可以在下面的链 ...

  3. 使用Visual Studio进行单元测试

    一.使用Visual Studio进行单元测试的几个建议 1.先写单元测试(依我愚见,应该是接口先行,如果有的话) -> 测试失败 -> 以最小的改动(即编写实际代码)使测试通过(而在VS ...

  4. Visual Studio 2010 单元测试目录

    单元测试的重要性这里我就不多说了,以前大家一直使用NUnit来进行单元测试,其实早在Visual Studio 2005里面,微软就已经集成了一个叫Test的专门测试插件,经过几年的发展,这个工具现在 ...

  5. Visual Studio 2010 单元测试之一---普通单元测试

    原文:Visual Studio 2010 单元测试之一---普通单元测试 本文以Visual Studio 2010为例,来介绍如何在Visual Studio里面进行单元测试. 首先来介绍普通单元 ...

  6. Visual Studio 2010 单元测试--运行测试并查看代码覆盖率

    原文:Visual Studio 2010 单元测试--运行测试并查看代码覆盖率 运行测试并查看代码覆盖率对程序集中的代码运行测试时,可以通过收集代码覆盖率数据来查看正在测试的项目代码部分. 运行测试 ...

  7. MVC 基本工具(Visual Studio 的单元测试、使用Moq)

    3.Visual Studio 的单元测试 有很多.NET单元测试包,其中很多是开源和免费的.本文打算使用 Visual Studio 附带的内建单元测试支持,但其他一些.NET单元测试包也是可用的. ...

  8. [转]Visual Studio 2010单元测试(2)--运行测试并查看代码覆盖率

    Visual Studio 2010 单元测试--运行测试并查看代码覆盖率 运行测试并查看代码覆盖率对程序集中的代码运行测试时,可以通过收集代码覆盖率数据来查看正在测试的项目代码部分. 运行测试并查看 ...

  9. C# Note31: 如何使用Visual Studio做单元测试

    待更! 使用Visual Studio 2013进行单元测试--初级篇 带你玩转Visual Studio——单元测试(C++例)

随机推荐

  1. wcf读取message内容

    private string MessageToString(ref Message message) { WebContentFormat messageFormat = this.GetMessa ...

  2. [Android]豆瓣FM离线数据

    离线目录结构: /sdcard/Android/data/com.douban.radio下 ./cache/fileCaches: 离线音乐歌词(lyric) ./cache/images: 离线音 ...

  3. Basis 基础

    [转自 http://www.cnblogs.com/elegantok/archive/2008/11/03/1325163.html] 1 Basis1. Unix操作 更改口令 passwd e ...

  4. linux shell 字符串操作(长度,查找,替换)

    感谢原创,文章很有帮助. 转自:http://www.cnblogs.com/chengmo/archive/2010/10/02/1841355.html 一.判断读取字符串值 表达式 含义 ${v ...

  5. Python:笔记(1)——基础语法

    Python:笔记(1)——基础语法 我很抱歉有半年没有在博客园写过笔记了,客观因素有一些,但主观原因居多,再多的谴责和批判也都于事无补,我们能做的就是重振旗鼓,继续出发! ——写在Python之前 ...

  6. java程序实现Unicode码和中文互相转换

      根据前一篇的补充问题http://blog.csdn.net/fancylovejava/article/details/10142391 有了前一篇文章的了解,大概了解了unicode编码格式了 ...

  7. 【转】.net中快捷键的使用

    当前行行首:Home 当前行行尾:End 当前文档首行:ctrl+Home 当前文档尾行:ctrl+End 选中当前行: ① 按Home(定位到行首)然后按Shift+Dnd(行尾)   {从行首连选 ...

  8. [原创]java WEB学习笔记13:JSP介绍(背景,特点,原理)

    JSP介绍:(理解) 1)JSP背景 ①在很多动态网页中,绝大部分内容都是固定不变的,只有局部内容需要动态产生和改变: ②如果使用Servlet程序来输出只有局部内容需要动态改变的网页,其中所有的静态 ...

  9. 【leetcode刷题笔记】Insertion Sort List

    Sort a linked list using insertion sort. 题解:实现链表的插入排序. 要注意的地方就是,处理链表插入的时候尽量往当前游标的后面插入,而不要往前面插入,后者非常麻 ...

  10. 【Flask】SelectedField 同步数据库

    ## 如果不加入__init__函数会导致,SelectedField表单生成只有里面的内容不会和数据库同步(即数据库添加,删除字段时表单中数据项和初始化时一致.下一次重启app是才会同步) clas ...