上一篇中和大家分享了单元测试的理论、入门和一些实践(单元测试入门及实践)。
这篇中来介绍下更多的应用场景和使用Mock对象来进行快速的生成模拟对象来简化测试并解决一些问题。
场景分析:
我们假设一个应用场景,也是发生在项目中真实的案例。
真实对象不存在或不完备:新产品开发时,PlatformTeam只定义了接口,并未完全实现该接口中的方法。但FeatureTeam仍要进行自己的开发,并需要底层数据支持,等Platform开发完成再去编写自己的代码显然是不可能的,如何去处理这种依赖?
真实对象本身不确定:底层接品在设计之初也是不稳定的,通常在产品一个方法的返回值会跟据需求经常变动(这种情况在好的设计中出现的概率较小),如何在这种情况下保证上层代码的安全。
数据源不稳定:有些依赖数据会经常变化,或者根本拿不到,但方法本身仅是对数据的加工,不涉及取数据。
代码依赖:当要去初始化一个对象时,要初始化另外的依赖对象,还要解决依赖对象的依赖对象…,还不算完,一旦这个测试挂掉,恭喜,你又一次陷入穷尽的Debug中,因为无从只到是哪一步出错。
解决方案:
上一篇示例中已经提到这个问题,并通过一个简单的类来代替当前这个真实而复杂的类,返回我们期望的数据。这样做的好处,一个测试出错,总能立即找到它对应的方法,不用对外部类的行为负责任,因为单元测试是以方法为单位的,每一个类的方法都应被测试并保证返回结果的正确性,所以不必担心这样会产生测试不完整的问题。
为该对象创建一个接口(如果已有,跳过前两步);
让真实对象实现该接口;
在测试中,创建一个Mock的对象实现这个接口;
对要测试的方法进行指定返回值;
用Mock对象代替真实对象完成方法执行。
1 public interface IUserService |
实现类UserService调用了IUserRepository接口中的GetUser方法,但不对任何具体的实现类产生依赖。这就是一直鼓励的面向接口编程。
1 public class UserService : IUserService 2 { 3 private IUserRepository userRepository { get; set; } 4 5 public UserService(IUserRepository userRepository) 6 { 7 this.userRepository = userRepository; 8 } 9 10 #region IUserService Members 11 12 public User GetUser(string id) 13 { 14 return this.userRepository.GetUser(id); 15 } 16 17 public IList<User> GetUsers() 18 { 19 return this.userRepository.GetUsers(); 20 } 21 22 #endregion 23 } |
下面是IUserRepository接口的代码:
1 public interface IUserRepository 2 { 3 List<User> GetUsers(); 4 User GetUser(string Id); 5 6 void Save(User user); 7 } |