单元测试——使用模拟对象做交互测试

王朝学院·作者佚名  2016-08-28
窄屏简体版  字體: 小  |  中  |  大  |  超大  

最近在看.net单元测试艺术,我也喜欢单元测试,这里写一下如何在测试中使用模拟对象。

开发的过程中,我们都会遇到对象间的依赖,比如依赖数据库或文件,这时,我们需要使用模拟对象,来进行测试,我们可以手写模拟对象,当然也可以使用模拟框架。

假如有这样的一个需求,当用户登陆时,我需要对用户名和密码进行验证,然后再将用户名写入日志中。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

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);

}

}

上面的代码在验证完登陆信息后,需要向日志中写入用户名,由于写入日志可能依赖于文件或数据库,我们可能很难进行测试,所以,这里使用模拟对象进行测试。手写模拟对象,代码如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

[TestFixture]

public class MyLoginTest

{

[Test]

public void Vaild_Test()

{

MyLogin login = new MyLogin();

var log = new TestLog();

login.Log = log;

var userNmae = "admin";

var passWord = "123456";

var isLogin = login.Valid(userNmae, passWord);

Assert.AreEqual(isLogin, true);

Assert.AreEqual(log.Message, userNmae);

}

}

public class TestLog : ILog

{

public string Message;

public void Write(string message)

{

this.Message = message;

}

}

这里我们定义了一个对象TestLog对象,该对象就是一个模拟对像,继承了ILog接口。该测试中,一共进行了两项测试。一项是:验证用户名和密码是否输入正确。另一项是:验证用户写入日志的信息是否正确(比如应该写入用户名,结果把密码写入了日志,测试会无法通过)。

这里我们区分一下模拟对象与桩对象。上一节中,我们讲过桩对象的定义,那么模拟对象与桩对象是什么关系呢?

模拟对象与桩对象在写法上区别很小,关键在于模拟对象需要进行断言,也就是说模拟对象可以导致测试失败。桩对象只是为了方便测试所定义的一个对象,不需要进行断言,所以,桩对象永远不会导致测试失败。

上面的测试中,如果我们去掉最后一行代码,即我们不进行写入日志的断言,则该对象就是一个桩对象。

1

Assert.AreEqual(log.Message, userNmae);

上面的模拟对象是我们自己写的,自己写模拟对象比较费时,我们可以使用模拟框架进行编写。这里我使用了Rhino Mocks框架。如果要执行下面的代码,需要下载Rhino.Mocks.dll文件,然后直接引用即可。

测试框架这里我选用了NUnit框架。测试代码如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

[TestFixture]

public class MyLoginTest

{

[Test]

public void Mock_Vaild_Test()

{

MockRepository mock = new MockRepository();

var log = mock.DynamicMock<ILog>();

var userName = "admin";

var passWord = "123456";

using (mock.Record())

{

log.Write(userName);

}

MyLogin login = new MyLogin();

login.Log = log;

var isLogin = login.Valid(userName, passWord);

Assert.AreEqual(isLogin, true);

mock.VerifyAll();

}

这里我没有编写一个类去继承ILog接口,而是通过模拟框架,动态生成了一个ILog对象。代码是这句:

1

2

3

MockRepository mock = new MockRepository();

var log = mock.DynamicMock<ILog>();

这里便生成了Log对象。通过录制-回放的模式进行模拟对象测试,首先需要定义我们的期望行为,最后验证实际行为与期望行为是否一致。这里,需要录制我们期望行为,代码如下:

1

2

3

4

using (mock.Record())

{

log.Write(userName);

}

这里我们期望向日志中写入用户名。再通过回放来进行验证,代码如下:

1

mock.VerifyAll();

该方法会验证,期望向日志中写入的信息与实际向日志中写入的信息是否一致,如果不一致,测试失败。

这里我们便完成了使用模拟框架进行单元测试。如果我们不需要测试日志写入方法,则把模拟对象换成桩对象就可以了,生成桩对象的方法如下:

1

2

3

MockRepository mock = new MockRepository();

var log = mock.Stub<ILog>();

把回放的方法(mock.VerifyAll())去掉,就完成了模拟对象向桩对象的转变。注意,这里录制的代码还是需要的。

总结:编写模拟对象和桩对象是非常有意义的,使用框架可以帮助我们简化单元测试。一般情况下,一个测试中,可以有多个桩对象,但最好只有一个模拟对象。模拟对象太多,证明一个测试方法做了太多项测试,不利于维护测试代码,一旦代码变改,很容易使单元测试失败。

下一节,写一下测试框架的一些常用功能,如:如何模拟异常、如何模拟返回值等。。。

 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
 
© 2005- 王朝網路 版權所有 導航