What is a mocking framework? Why is it useful?

Today I want to talk about mocking frameworks and why they are useful. In order to do that I first need to propose a scenario to you in which we will be testing a piece of functionality in an application. I will show you how you would test it without a mocking framework, and then I will show you how to test it with one.

今天我想来谈谈mocking frameworks和为什么他们这么有用。为了说明这一点我首先给出了一些功能代码。我会展示给你如果不是用mocking framework我们需要如何测试。

Let's pretend we have a driver class like this: 假设我们有一个下面这样的driver类

Notice that our Driver class has a dependency on an interface called IVehicle. Let's define that interface.

注意我们的driver类有一个接口IVehicle的依赖。让我们定义这个接口

Now that we have our Driver class we need to write a couple unit tests for it. Before we can do that however, we have to figure out what we are going to pass into the constructor to satisfy the dependency on IVehicle. Many people would think at this point that they need to finish whatever component implements the IVehicle interface before they can test the Driverclass. That is not the case. In fact, if you passed a completed component for IVehicle you might as well not even write unit tests. The whole point of unit testing is to test things in isolation. Testing the Driver class with a full-fledged version of IVehicle is not unit testing; that is getting closer to integration testing which is a whole different ball game. If a test on your Driver class fails, you couldn't be sure it was the Driver class's fault or if something went wrong within the class that was passed in as a dependency.

既然我们有了我们的dirver类就需要为它写单元测试。但是在我们写之前我们需要搞清楚传入什么参数才可以满足IVehicle的依赖。许多人可能认为在他们测试driver类之前无论如何都需要完成执行IVehicle接口。事实上,如果你传递一个完整的IVehicle进去你可能就不是在写单元测试。单元测试的重点是独立测试。如果你的dirver类失败了,你可能无法确认是dirver类失败了还是传入其中的依赖类操作失败了。

The fact that the dependency is an interface makes this an easy fix. We will just create a fakeimplementation of IVehicle.

依赖是一个接口使得它看来是容易解决的。我们可以仅仅创建一个IVehicle的假操作

Notice the global integers we defined? In our unit tests we would like a way to verify that the HonkHorn() and ApplyBrakes() methods actually got called and how many times they got called. Now that we have something we can pass into our Driver class when we instantiate it, we can write our tests. We want to test two behaviors: Can a driver evade trouble? And can a driver evade trouble while alerting the offending driver to his mistake?

注意到全局integers的定义了吗?在我们的单元测试中我们期望确定honkhorn()和applybrakes()方法的确得到了调用。既然我们有了在实例化dirver类时可以传递给driver类的数据,我们就可以写自己的测试了。我们想测试两个操作:Can a driver evade trouble? And can a driver evade trouble while alerting the offending driver to his mistake?

This is all great. We have a successful unit test that verifies both that the EvasiveManeuvers()method returns true and that the ApplyBrakes() method on the implementing class of IVehicle was called exactly one time in both tests, as well as the HonkHorn() method was called exactly once in the second test and never in the first test. Take note that in real Test Driven Development (TDD) we would have written the tests first, and then wrote the code we were testing. However, for our purposes it was easier to show you what we had and how we might test it.

There is one thing that is rather annoying about writing tests in this manner. We have to write a special class (FakeVehicle) in order to even instantiate Driver. Writing a special class for every dependency can get VERY tiring. This is where a mocking framework comes in. The framework I am going to use is called Moq; it's syntax is unique but after using it for a bit I think you'll agree that it is very fluent and easy to write. Make sure you have the Moq.dll assembly referenced in your project and that your unit test class has the using Moq; declaration at the top. Now let's rewrite our unit tests to use Moq and then I will explain it in greater detail afterward.

这样做很棒。我们成功进行了单元测试来保证EvasiveManeuvers()方法返回true并且ApplyBrakes和HonkHorn也被成功调用了。

但是这样的话我们就必须为了实例化Driver来写一个特定的类FakeVehicle。为每一个依赖写特定的类是非常辛苦的。这就是mocking framework出现的原因了。

Believe it or not, with only a few lines of code we got rid of the need for the FakeVehicleclass to exist. Moq took our interface and dynamically built a class that implements it behind the scenes. By default all the members of the class it built would return default values. Since the default value of a Boolean is false, the HonkHorn() and ApplyBrakes() methods on the mocked class would return false. Obviously we want them to return true when called. To accomplish this we simply make use of Moq's Setup() method. This method accepts a lambda expression which allows you to navigate to the method on the interface that you want to define in a strongly-typed manner. If this is confusing, picture the alternative; if Moq did not accept a lambda expression then you would have to give it a method name in a magic string, like this:

不论相不相信,只有几行代码就可以代替FakeVehicleclass的需要了。moq用了我们的接口并且动态创建了一个类来在后台执行。默认的所有的类成员都返回默认值。因为boolean的默认值是false,所以HonkHorn()和 ApplyBrakes()方法就都会返回false。显然的我们想调用时另其返回true。为了实现这点,我们只是简单实用了moq的setup()方法。

Once you have all that setup you can instantiate the component you are testing and pass in the mocked object by calling mock.Objectmock.Object is a really generic sounding property to store the mocked object we created, but that's where it is stored. Do not try to pass the instance of Mock<T> itself into your component. It can be confusing at first but remember that your Driver needs an instance of IVehicleMock<T> is not of type IVehicle, it is of type Mock<T>. To get the created instance of IVehicle you must access the Object property on your instance of Mock<T>. In other words:

一旦你初始化了所有想测试的组建并且传递了mocked object。mock.object就是一个保存在mocked object中的属性。不要将Mock<T>本身传递到你的组建中去。driver需要的是IVehicle类型的实例,Mock<T>不是这个类型的,而是 Mock<T>类型的。为了得到IVehicle实例,你必须访问Mock<T>实例属性object,换句话说:

After that you can go ahead and perform whatever functions you intend to test. In our case we called driver.EvasiveManeuvers(alertOffendingDriver) and tested that the returned result was true. The next thing we tested was how many times the HonkHorn() and ApplyBrakes() methods were called on the dependency class. To do this with the fake class we created we had to actually add fields to track how many times the methods were called. With Moq we don't have to do anything. It automatically tracks how many times we called the method. All we have to do is call mock.Verify() passing in a lambda with the method we want to check and the number of times we expected it to be called. If the verify fails it will throw an exception, causing our test to fail.

在那之后你可以去执行任何你想测试的功能。在我们的例子中调用driver.EvasiveManeuvers(alertOffendingDriver) 并且返回true。接下来的事情是测试HonkHorn() and ApplyBrakes()调用了几次。实用moq我们不需要做任何事情,它会自动追踪我们调用了多少次这个方法。我们需要做的是调用mock.Verify()

In its most basic form that is the usage of Moq. Hopefully now you can see why mocking frameworks are so valued and how Moq accomplishes its task. Post any questions in the comments below. I assure you that I read each and every one of them that same day.

 

posted @ 2015-01-22 13:57  如来藏  阅读(225)  评论(0编辑  收藏  举报