新手单元测试教程
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以及第三方类库等。
|