新手单元测试教程

 

1.为什么需要单元测试?
单元测试基于它可以: 减少程序潜在的问题; 程序即说明文档; 
但它可能带来的一个问题是:你会觉得编写这些测试程序很费时间,但相对于软件后期的维护,你会发现这样做其实是值得的。

先说介绍一下,Assert类所在的命名空间为
Microsoft.VisualStudio.TestTools.UnitTesting 在工程文件中只要引用Microsoft.VisualStudio.QualityTools.UnitTestFramework.dll就可以使用了

1、 AreEqual:方法被重载了N多次,主要功能是判断两个值是否相等;如果两个值不相等,则测试失败。
2、 AreNotEqual:方法被重载了N多次,主要功能是判断两个值是否不相等;如果两个值相等,则测试失败。
3、 AreNotSame:引用的对象是否不相同;如果两个输入内容引用相同的对象,则测试失败.
4、 AreSame:引用的对象是否相同;如果两个输入内容引用不相同的对象,则测试失败.
5、 Fail:断言失败。
6、 Inconclusive:表示无法证明为 true 或 false 的测试结果
7、 IsFalse:指定的条件是否为 false;如果该条件为 true,则测试失败。
8、 IsTrue:指定的条件是否为 true;如果该条件为 false,则测试失败
9、 IsInstanceofType:测试指定的对象是否为所需类型的实例;如果所需的实例不在该对象的继承层次结构中,则测试失败
10、 IsNotInstanceofType: 测试指定的对象是否为所需类型的实例;如果所需的实例在该对象的继承层次结构中,则测试失败
11、 IsNull:测试指定的对象是否为非空
12、 IsNotNull:测试指定的对象是否为非空

主要针对public共有函数

例子代码:


public string GetName()
{
return "Hello World";
}

public int ToInt(string value)
{
int result = 0;
if (!string.IsNullOrWhiteSpace(value))
{
if (!int.TryParse(value, out result))
{
throw new Exception("文本内容无法转换为Int类型。");
}
}
else
{
throw new Exception("文本不能为空。");
}
return result;
}

测试代码:

[TestMethod]
public void GetName()
{
HelloWorld helloworld = new HelloWorld();
var name = helloworld.GetName();
Assert.AreEqual(name, "Hello World");
}

[TestMethod]
public void ToInt()
{
HelloWorld helloworld = new HelloWorld();
string value = "5";
int expected = 5;//预期的值
int actual;//实际的值
actual = helloworld.ToInt(value);
Assert.AreEqual(expected, actual);
value = "5.5";
expected = 5;
actual = helloworld.ToInt(value);
Assert.AreEqual(expected, actual);
}



2

一个共有的 Public 方法实现某一主要功能,但是由于该功能的实现非常复杂,需要很多的辅助类,辅助方法。由于代码封装性的需求,我们通常需要把这些辅助的类方法定义为非Public,静态static的(非必须,但是静态方法会提升性能),如 private, internal 等。

但是这也带来了一个问题,如何对这些非 public 的类,方法进行单元测,毕竟这些才是完成逻辑的代码?

PrivateObject and PrivateType

PrivateObject主要测试私有函数

PrivateType主要测试私有static 函数

主要用这个两个类

例子代码:

private static Double Format(Double fileSize)
{
Double size = fileSize;
size = size / 1024 / 1024;
size = (int)(size * 10 + 0.5) / 10.0;
return size;
}

private int Add(int x,int y)
{
return x + y;
}

单元测试代码:

[TestMethod]
public void Format()
{
PrivateType pt = new PrivateType(typeof(Medium));
var value = pt.InvokeStatic("Format", 4.3);
Assert.AreEqual(value, 0.0);
}

[TestMethod]
public void Add()
{
PrivateObject po = new PrivateObject(new Medium());
var value = po.Invoke("Add", 3,6);
Assert.AreEqual(value, 9);
}
3
为了应用程序的安全考虑,一些方法属性会被定义为private,那么private属性需要如何访问呢?
GetParameterStrValue()方法主要用于调用私有属性及方法
例子代码:
public class Demo
{
Demo();

private string _test="cs";
private string test 
{
get { return _test; }
set { _test = value; }
}
}

[TestMethod]
public void GetTest()
{
string Test="cs";
Demo de =new Demo();
var result = de.GetParameterStrValue("test");
Assert.AreEqual(Test, result);
}

Fakes有两种形式:stub 和 shim。。

一、shim
以下将模拟DateTime的Now属性,假设我现在需要在活动服务类ActivityService添加一个方法验证某个线下活动是否过期。

1. 打开VS2012,创建单元测试项目FakesTesting,我这是测试先行。重命名项目自动生成的类UnitTest1为ActivityServiceTest,将TestMethod1改为IsExpireTest(是否过期).

2. 添加代码“ActivityService service = new ActivityService();”并使用VS快捷功能为我们创建ActivityService 类

3. 添加Fakes,由于DateTime位于System程序集,因而将添加System的Fake程序集(右键System程序集), 然后在测试类“using System.Fakes;”

4. 编写测试代码如下


using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Fakes;
using Microsoft.QualityTools.Testing.Fakes;

namespace FakesTesting.Test
{
[TestClass]
public class ActivityServiceTest
{
[TestMethod]
public void IsExpireTest()
{
ActivityService service = new ActivityService();
bool actual = service.IsExpire();
Assert.IsFalse(actual);

using (ShimsContext.Create())
{
ShimDateTime.NowGet = () => new DateTime(2014, 5, 5);
actual = service.IsExpire();
Assert.IsFalse(actual);
}
}
}
}

5. 然后编写ActivityService类


public class ActivityService
{
public DateTime BeginTime { get; set; }

public ActivityService()
{
this.BeginTime = new DateTime(2014, 3, 3); //仅作演示,无意义
}

public bool IsExpire()
{
return BeginTime >= DateTime.Now;
}
}


二、Stub
现在假设ActivityService类有一个方法获取是否还能报名,但是它依赖于仓储IActivityRepository(只有遵循依赖反转与接口隔离原则的代码才好使用Stub填充外部依赖)提供的RegisterNumber方法。

1. IActivityRepository接口(新建IRepositories项目并添加该接口)


public interface IActivityRepository
{
/// <summary>
/// 已报名人数
/// </summary>
int RegisterNumber();
}

2. 而我们的单元测试现在不能依赖具体(实际环境中的Repository可能对测试带来影响),这时候就能使用Stub来填充该接口了,添加IRepositories引用,然后与上一个Demo一样的添加IRepositories的Fakes程序集。

3. 在测试类中添加Using代码

using IRepositories;
using IRepositories.Fakes;
4. 编写测试代码


[TestMethod]
public void CanRegisterTest()
{
StubIActivityRepository repository = new StubIActivityRepository();
ActivityService service = new ActivityService(repository);

//如果已报名人数小于最多可报名数量则不能再报名,断言CanRegister方法应为True
repository.RegisterNumber = ()=> 20;
bool actual = service.CanRegister();
Assert.IsTrue(actual);

//如果已报名人数大于等于最多可报名数量则不能再报名,断言CanRegister方法应为False
repository.RegisterNumber = () => 50;
actual = service.CanRegister();
        Assert.IsFalse(actual);
      }


5. ActivityService代码:


public class ActivityService
{
public DateTime BeginTime { get; set; }

/// <summary>
/// 最多可报名数量
/// </summary>
private int maxCount = 50;
private IActivityRepository repository;

public ActivityService()
{
this.BeginTime = new DateTime(2014, 3, 3); //仅作演示,无意义
}

public ActivityService(IActivityRepository repository)
{
// TODO: Complete member initialization
this.repository = repository;
}

public bool IsExpire()
{
return BeginTime >= DateTime.Now;
}

public bool CanRegister()
{
return repository.RegisterNumber() < this.maxCount;
}
}



总结
stub用于我们可控的代码,shim用于不可控的,例如.NET Framework以及第三方类库等。

 

posted @ 2016-04-22 10:03  心灬无痕  阅读(359)  评论(0编辑  收藏  举报