在讲Test Double以前,得先上搞清楚另外两个
SUT:System Under Test/Software Under Test 【待测程式】,在Unit Test角度下待测程式会是method
DOC:Depended-on Component【相依元件】,例如:成立订单函数,如果订单失败异常会写入log,log函数就是成立订单函数的DOC
在实务上笔者在写单元测试的时候,因为SUT会去呼叫其他的物件也会呈现出SUT依赖DOC,
在测试SUT势必会有DOC存在,搞得测试很複杂。
目前测试替身有五种Dummy Object、Test Stub、Test Spy、Fake Object与Mock Object
笔者目前是常用到Stub、Mock、Fake Object。
Dummy Objects用法:测试中必续传入的物件,实际上这些物件根本不会用到,只是为了能够调用背侧物件而必须传入的一个东西【目前没用到】。
Stub用法:回传固定的值。
public class StubUserRepository : IUserRepository { public string GetUserRole(string username) { return "administrator"; } public List<User> GetAllUsers() { return new List<User>() { new User{ Role = "administrator", Name = "admin"}, }; } } public interface IUserRepository { string GetUserRole(string username); } public class User { public string Name { get; set; } public string Role { get; set; } }
Fake用法:接近提供原始物件较简单实作方式。
public class FakeUserRepository : IUserRepository { public string GetUserRole(string username) { if (username == "admin") return "administrator"; else return "contributor"; } } public interface IUserRepository { string GetUserRole(string username); }
Spy用法:把spy想成是一个偷偷纪录呼叫过程的Stub,宣告被spy的物件,某个函式被呼叫的时候回传某个值。
public class SpyUserStore : IUserRepository { private static int Counter { get; set; } public SpyUserStore() { Counter = 0; } public string GetUserRole(string username) { if (Counter >= 1) throw new Exception("Function called more than once"); Counter++; if (username == "admin") return "administrator"; else return "contributor"; } }
Mock用法:用动态程式方式、提供类似Dummy,Stub,Spy功能
var _memberRepository = Substitute.For<IMemberRepository>(); var _memberService = new MemberService(_memberRepository); var _setReturnMember = new Member() { UserId = "test001", Password = "test001", Name = "test", Email = "abc@gmail.com" }; _memberRepository.GetByMember(userid,password).Returns(_setReturnMember);
最后总结一下,测试替身可以解决单元测试的那些问题?
测试替身是为了解决不用真的使用DOC可以提升测试执行速度,避免速度太慢另外其实在真的写测试,也不用太在意你是用哪一个测试替身...
参考来源
搞笑谈软工-测试替身jyt0532's Blog-测试替身