码迷,mamicode.com
首页 > 其他好文 > 详细

Unit Test Via Visual Studio-Part5

时间:2015-10-31 22:59:47      阅读:326      评论:0      收藏:0      [点我收藏+]

标签:

本文主要介绍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 0;
        }
    }
}

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

  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(0, 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 == 1)
            {
                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(1);
            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 = 0; 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 = 0;
                ShimLoopHandler.AllInstances.Do = (@this) =>
                {
                    times++;//每进入一次加一次
                };
                var givenTimes = 100;
                new DemoClass().DoSomething(givenTimes);
                Assert.AreEqual(times, givenTimes);
            }
        }

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

3.3 并行编程测试注意事项

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

 public void DoSomething(int times)
        {
            Parallel.For(0, times, (item) =>//并行执行
            {
                handler.Do();
            });
        }

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

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

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

技术分享

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

Unit Test Via Visual Studio-Part5

标签:

原文地址:http://www.cnblogs.com/kmpp/p/Unit_Test_Tricks.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!