Unit Test
一、What
单元测试是:一小段代码检验特定场景下方法的行为是否正确。通常从正确结果、边界条件、性能、异常等方面思量。
二、Why
•改善设计
单一职责
改善接口的设计
•减少debug时间
三、Where
UT也要抓住重点
•公共代码, 如Foundation
•疑难点
•重复发生bug
四、How
被测试代码
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
public class TMethod { public int Add(int a, int b) { return (a + b); } public int Devide(int a, int b) { var c = a / b; return c; } }
测试代码,使用了Nunit框架
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
[TestFixture] public class TMethodTest { private int a, b; private TTT.TMethod t; [SetUp] public void Initialize() { //First 创建测试条件,创建对象、分配资源 a = 4; b = 2; t = new TTT.TMethod(); } [TearDown] public void CleanUp() { //Fourth 释放资源 } [Test][Explicit] public void Add() { //Second 调用被测试方法 var actual = t.Add(a, b); //Third 验证结果 Assert.AreEqual(6, actual); Console.Out.WriteLine("It's Ok"); } [Test][ExpectedException(typeof(DivideByZeroException))] public void DevideZero() { Console.Error.WriteLine("It's wrong"); var actual = t.Devide(a, 0); } }
五、Mock
Mock,中文模拟,清晰的表达了mock对象的用途。
a)准备测试材料
/// <summary> /// 数据结构 /// </summary> public class Person { public string Id; public string FirstName; public string LastName; public string Say; public Person(string newId, string fn, string ln) { Id = newId; FirstName = fn; LastName = ln; } public Person(string newId, string fn, string ln, string say) { Id = newId; FirstName = fn; LastName = ln; Say = say; } }
/// <summary> /// 行为接口 /// </summary> public interface IPersonRepository { List<Person> GetPeople(); Person GetPersonById(string id); }
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
/// <summary> /// 被测试对象 /// </summary> public class PersonService { private IPersonRepository personRepos; public PersonService(IPersonRepository repos) { personRepos = repos; } public List<Person> GetAllPeople() { return personRepos.GetPeople(); } public List<Person> GetAllPeopleSorted() { List<Person> people = personRepos.GetPeople(); people.Sort(delegate(Person lhp, Person rhp) { return lhp.LastName.CompareTo(rhp.LastName); }); return people; } public Person GetPerson(string id) { try { return personRepos.GetPersonById(id); } catch (ArgumentException) { return null; // no person with that id was found } } }
b)mock实例
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
//Mock对象实现IPersonRepository接口 public class PersonReposMock:IPersonRepository { private List<Person> _lstPerson; public PersonReposMock(List<Person> lstPerson) { _lstPerson = lstPerson; } public List<Person> GetPeople() { return _lstPerson; } public Person GetPersonById(string id) { return _lstPerson.Find(p => p.Id.Equals(id)); } }
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
[TestFixture] public class PeopleMockTest { private IPersonRepository personReposMock; private Person onePerson = new Person("1", "Wendy", "Whiner"); private Person secondPerson = new Person("2", "Aaron", "Adams"); private List<Person> peopleList; [SetUp] public void Initialize() { //准备数据 peopleList = new List<Person>(); peopleList.Add(onePerson); peopleList.Add(secondPerson); personReposMock = new PersonReposMock(peopleList); } [Test] public void TestGetAllPeople() { PersonService service = new PersonService(personReposMock); Assert.AreEqual(2, service.GetAllPeople().Count); } [Test] public void TestGetAllPeopleSorted() { PersonService service = new PersonService(personReposMock); List<Person> people = service.GetAllPeopleSorted(); Assert.IsNotNull(people); Assert.AreEqual(2, people.Count); Person p = people[0]; Assert.AreEqual("Adams", p.LastName); } [Test] public void TestGetSinglePersonWithValidId() { PersonService service = new PersonService(personReposMock); Person p = service.GetPerson("1"); Assert.IsNotNull(p); Assert.AreEqual(p.Id, "1"); } [Test] public void TestGetSinglePersonWithInalidId() { PersonService service = new PersonService(personReposMock); // The only way to get null is if the underlying IPersonRepository // threw an ArgumentException Assert.IsNull(service.GetPerson(null)); } }
c)DynamicMock实例
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
[TestFixture] public class PeopleServiceTest { private DynamicMock personReposMock; private Person onePerson = new Person("1", "Wendy", "Whiner"); private Person secondPerson = new Person("2", "Aaron", "Adams"); private List<Person> peopleList; [SetUp] public void Initialize() { //准备数据 peopleList = new List<Person>(); peopleList.Add(onePerson); peopleList.Add(secondPerson); // 使用接口 IPersonRepository 创建动态 Mock 对象 personReposMock = new DynamicMock(typeof(IPersonRepository)); } [Test] public void TestGetAllPeople() { // 当调用 "GetPeople" 方法的时候,返回一个 // 预定义列表 personReposMock.ExpectAndReturn("GetPeople", peopleList); // 使用 IPersonRepository 的 Mock 对象来创建 PersonService PersonService service = new PersonService( (IPersonRepository)personReposMock.MockInstance); // 调用方法并进行断言 Assert.AreEqual(2, service.GetAllPeople().Count); } [Test] public void TestGetAllPeopleSorted() { personReposMock.ExpectAndReturn("GetPeople", peopleList); PersonService service = new PersonService( (IPersonRepository)personReposMock.MockInstance); // This method really has "business logic" in it - the sorting of people List<Person> people = service.GetAllPeopleSorted(); Assert.IsNotNull(people); Assert.AreEqual(2, people.Count); // Make sure the first person returned is the correct one Person p = people[0]; Assert.AreEqual("Adams", p.LastName); } [Test] public void TestGetSinglePersonWithInalidId() { // 当传递一个 null 调用 "GetPersonById" 方法的时候 // 抛出异常 ArgumentException personReposMock.ExpectAndThrow("GetPersonById", new ArgumentException("Invalid person id."), null); PersonService service = new PersonService( (IPersonRepository)personReposMock.MockInstance); // The only way to get null is if the underlying IPersonRepository // threw an ArgumentException Assert.IsNull(service.GetPerson(null)); } [Test] public void TestGetSinglePersonWithValidId() { // 当调用 "GetPerson" 方法的时候返回一个预定义的 Person personReposMock.ExpectAndReturn("GetPersonById", onePerson, "1"); PersonService service = new PersonService( (IPersonRepository)personReposMock.MockInstance); Person p = service.GetPerson("1"); Assert.IsNotNull(p); Assert.AreEqual(p.Id, "1"); } }
六、Efficient
七、Nunit
Nunit大名鼎鼎,就不献丑了。
八、Summary
Test case先行,product code在后,迎合了Tdd的思想。
关于测试框架,MsTest也好、听说的xUnit也好,其实测试思维才是重点。
Mark:
参考、摘取了《单元测试之道(Nunit)》,和一些blog的内容。
现代足球中场兼进球,兼防守扫荡。软件开发也要求产品意识,质量测试。