最近在看.net单元测试艺术,我也喜欢单元测试,这里写一下如何在测试中解除对象间的依赖。
假如有这样的一个需求,当用户登陆时,我需要对用户名和密码进行验证,然后再将用户名写入日志中。
public class MyLogin { public bool Valid(string userName, string passWord) { var isValid = userName == "admin" && passWord == "123456"; WriteLog(userName); return isValid; } private void WriteLog(string message) { //....写入数据库或文件中 } }
上面代码中,如果我们写单元测试时会发现,WriteLog方法依赖于文件或数据库时,这时测试会比较困难一些。首先我们需要对解除依赖,再进行测试。代码如下:
public class MyLogin { public ILog Log { get; set; } public bool Valid(string userName, string passWord) { var isValid = userName == "admin" && passWord == "123456"; Log.Write(userName); return isValid; } } public interface ILog { void Write(string message); }
这时我们引入了ILog接口,在调用Valid方法前,需要对属于Log进行赋值,这里,我们便解除了对象之间的依赖。我们开始写单元测试,测试中,我们需要自己模拟一个桩对象。
桩对象是对系统中现有依赖项的一个替代品,可人为控制,通过使用桩对象,无需涉及依赖项,即可直接对代码进行测试。
测试框架这里我选用了NUnit框架。测试代码如下:
[TestFixture] public class MyLoginTest { [Test] public void Vaild_Test() { MyLogin login = new MyLogin(); login.Log = new TestLog(); var isLogin = login.Valid("admin", "123456"); Assert.AreEqual(isLogin, true); } } public class TestLog : ILog { public void Write(string message) { //nothing } }
这里我定义了一个类TestLog,这个类所生成的对象就是一个桩对象,桩对象的目的是为了替换测试中的依赖项,有些时候,依赖项可能需要文件或某些配置,导致很难测试,所以,为了方便测试,我们使用了桩对象。
上面的例子中,我们通过接口完成了对象间的依赖解除,再通过属性完成对接口的赋值,如果不使用属性,我们还可以用别的方法对接口进行赋值。
- 通过构造函数进行赋值。
public MyLogin(ILog log) Log = log;
在测试中,初始化Mylgoin对象时,把桩对象传给构造函数即可。
2. 通过工厂模式进行赋值。
通过工厂类来生成ILog对象,并且工厂类要允许传入ILog对象,代码如下:
public class MyLogin { public bool Valid(string userName, string passWord) { var isValid = userName == "admin" && passWord == "123456"; var log = Factory.CreateLog(); log.Write(userName); return isValid; } } public interface ILog { void Write(string message); } public class Factory { private static ILog iLog; public static ILog CreateLog() { return iLog; } public static void SetLog(ILog log) { iLog = log; } }
测试类中,要对Factory的SetLog方法进行赋值,其它操作基本不变。
3. 使用虚方法或IOC容器。
这些操作需要根据实际情况去使用。不要因为测试去改变你的设计(过度测试),除非你的设计无法测试。
总结: 对于开发人员来讲,单元测试是很重要一项工作。它会让你的代码结构更加清晰,代码的可读性也会更好。下一节,写一下如何使用框架进行测试,毕竟写桩对象还是要花很多时间的。