swift 引用swift_Swift嘲笑

swift 引用swift

When I started learning about what defines a good unit test, an important concept to remember was that good unit tests should be isolated and that the goal of a single unit test is to verify that a specific unit of our code works properly while being isolated from the rest of the system. This means that if for example, we have a Shop class that contains a method sellItem(:item) and if we want to write a unit test that will test the behavior of sellItem(:item), our unit test should not care at this point about the implementation details of the item argument that sellItem(:item) accepts. If our unit test cares about the implementation details of the item argument, this means that our unit test is not isolated and actually at this point, we might not be able to call it a unit test anymore. It’s more of an integration test now.

当我开始了解定义好的单元测试的内容时,要记住的一个重要概念是,应该将好的单元测试隔离开来,并且单个单元测试的目标是要验证我们代码中的特定单元在与系统的其余部分。 这意味着,例如,如果我们有一个包含方法sellItem(:item)Shop类,并且如果我们要编写将测试sellItem(:item)行为的单元测试,则我们的单元测试不应该在此关于sellItem(:item)接受的item参数的实现细节。 如果我们的单元测试关心item参数的实现细节,这意味着我们的单元测试不是孤立的,实际上在这一点上,我们可能再也不能称其为单元测试。 现在更多的是集成测试。

To make our unit test isolated and not forced to work with other parts of our code that might introduce other problems, we have to fake those other parts that we don’t want to depend on or care about. During my development years, there has been lots of confusion about what does faking means here. After reading a few books and articles on the subject, I found that there are many ways to fake the behavior of the objects that we don’t care about. Those objects, in this case, can get many different names: “mocks, stubs, dummies, fakes, spies,..”. I used to get confused all the time when I heard those words. At some point, they all seemed very similar to me. Many times I used to refer to stubs as mocks and vice versa, but thankfully I learned more about the differences as I progressed in my career. I hope that I have a better understanding of those concepts now and that I can help you understand them too.

为了使我们的单元测试孤立,并且不被迫与可能导致其他问题的代码其他部分一起工作,我们必须伪造我们不想依赖或关心的其他部分。 在我的发展岁月中,关于伪造在这里的含义一直有很多困惑。 阅读有关该主题的几本书和文章后,我发现有很多方法可以伪造我们不关心的对象的行为。 在这种情况下,这些对象可以得到许多不同的名称:“ 嘲笑存根假人假货间谍 ....”。 当我听到这些话时,我总是感到困惑。 在某些时候,他们似乎都和我很相似。 很多时候,我曾经将存根称为模拟 ,反之亦然,但幸运的是,随着职业的发展,我了解了更多有关差异的知识。 我希望我现在对这些概念有了更好的理解,也希望能帮助您理解它们。

测试双打 (Test Doubles)

Gerard Meszaros’s has a nice definition for what does faking an object mean in his book “xUnit Test Patterns”. He uses the term “Test Double” to refer to any kind of fake or pretend object that is used in place of a real object for testing purposes.

Gerard Meszaros在他的 “ xUnit测试模式” 一书中伪造对象的含义有一个很好的定义。 他使用“ Test Double ”一词来指代用于测试目的的任何种类的假冒或伪装对象,用以代替真实对象。

There are different types of “Test Doubles” that we might need for writing our tests. While the definitions can be slightly different among developers, I would love to share my understanding in this article and hear any other definitions or thoughts about them. This is a list of the most common “Test Doubles” used in the testing world:

编写测试可能需要不同类型的“ 测试双打 ”。 尽管开发人员之间的定义可能略有不同,但我还是希望在本文中分享我的理解,并听听关于它们的任何其他定义或想法。 这是测试领​​域中最常用的“ 测试双打 ”列表:

  • Dummy Object: An object that does nothing. The main importance of creating a dummy object is for our code to compile. We can have some parameters to fill and in this case, we need to create some dummy objects so that our code can successfully compile. Those dummy objects can be of the type class or struct and they might conform to any number of protocols but they don’t have any functionality.

    虚拟对象:不执行任何操作的对象。 创建虚拟对象的主要重要性在于对我们的代码进行编译。 我们可以填充一些参数,在这种情况下,我们需要创建一些虚拟对象,以便我们的代码可以成功编译。 这些伪对象可以是结构类型,并且它们可以符合任何数量的协议,但是它们没有任何功能。

  • Fake Object: An object that is an actual working implementation of a class for example, but the implementation takes some sort of shortcut that makes testing easier. The most common example is an in-memory database fake object. Fake objects can make our tests run faster. An in-memory database runs faster than a real one.

    伪对象:一个对象,例如是一个类的实际有效实现,但是该实现采用某种快捷方式,可以使测试更加容易。 最常见的示例是内存数据库伪造对象。 伪造的对象可以使我们的测试运行更快。 内存数据库的运行速度比真实数据库快。

  • Stub Object: An object that always returns fixed data. For example, if you want to write a test that verifies the correct creation of a model object after decoding some JSON data, it’s better in this case to create an object that will always return a fixed JSON response. This helps to make your tests faster and always reliable.

    存根对象:始终返回固定数据的对象。 例如,如果要编写一个在解码一些JSON数据后验证模型对象正确创建的测试,则在这种情况下最好创建一个始终返回固定JSON响应的对象。 这有助于使测试更快且始终可靠。

  • Mock Object: An object that uses behavior verification. Mock objects are widely used to verify that an object was initialized successfully, that specific methods were called inside the object or that some methods were called a certain number of times. Mock objects track all the data so that you can verify it later but they don’t do any meaningful action. Mocks are very powerful because we set them up with a series of expectations, trigger some action, and then we will be able to inspect all the work that happened in a sealed black box with no actual work happening. They give us just enough feedback to build real objects.

    模拟对象:使用行为验证的对象。 模拟对象被广泛用于验证对象是否已成功初始化,在对象内部调用了特定方法或某些方法被调用了一定次数。 模拟对象跟踪所有数据,以便您稍后可以对其进行验证,但它们不会执行任何有意义的操作。 模拟功能非常强大,因为我们设定了一系列期望,触发了一些动作,然后我们便可以检查在密封的黑盒子中发生的所有工作,而没有实际的工作发生。 它们给我们足够的反馈来构建真实的对象。

  • Spy Object: A spy object is similar to a mock object except that it can make actual work happen. A spy object tracks the behavior of an object (to check if specific methods were called successfully or called a certain number of times) and also does an actual work (like sending a real network request).

    间谍对象: A 间谍对象类似于模拟对象,只是它可以使实际工作发生。 间谍对象跟踪对象的行为(以检查特定方法是否被成功调用或被调用了一定次数),并且还执行实际工作(例如发送真实的网络请求)。

Regardless of which implementation you choose to create your test double, all test doubles need to conform to the correct protocols required for them to stand in place for the real type. All types of test doubles are important, but “mocks” and “stubs” are maybe the two most common types that are being used in the world of testing.

无论选择哪种实现创建测试对,所有测试对都必须符合正确的协议,以使它们对真实类型有效。 所有类型的测试双打都很重要,但“ 模拟 ”和“ 存根 ”可能是测试领域中使用的两种最常见的类型。

In this article, we will talk more about mocking in Swift and see some examples.

在本文中,我们将更多地讨论Swift中的模拟并看到一些示例。

模拟 (Mocking)

You probably have heard the term “mocking” so many times until now. It is so popular today and is widely used that many developers use it to refer to the act of creating a test double of some sort.

到目前为止,您可能已经听过很多次“ 嘲笑 ”一词。 它在当今如此流行并且被广泛使用,以至于许多开发人员都使用它来指代创建某种形式的双重测试的行为。

If we recall, the main job of a mock object is to verify that some sort of behavior happened on this mock object. Mocks simulate the behavior of a real object but they don’t cause any actions to take place.

回想一下,模拟对象的主要工作是验证该模拟对象是否发生了某种行为。 嘲笑模拟真实对象的行为,但不会引起任何动作。

Remember: An important characteristic of a good unit test is that it is isolated and that it tests one single unit of code at a time. Things outside the tested code are faked or simulated because unit tests shouldn’t have dependencies on outside systems. If we have one object that we want to test and this object depends on some other objects, to make sure that a unit test is focusing only on verifying the behavior of our object and doesn’t get complicated by using instances of the dependencies, we tend to simulate the behavior of those dependencies, i.e. mocking them.

切记 :好的单元测试的一个重要特征是它是隔离的,并且一次只能测试一个代码单元。 由于单元测试不应依赖于外部系统,因此被测试代码外部的内容将被伪造或模拟。 如果我们有一个要测试的对象并且该对象依赖于其他对象,请确保单元测试仅专注于验证对象的行为,并且不会因使用依赖项实例而变得复杂,倾向于模仿这些依赖的行为,即嘲笑

Let’s start with an example: people buying books from a BookStore. Our BookStore has an inventory of 1000 books and every time a customer buys a book the inventory’s count gets decreased by one. This is how our Buyer class may look like:

让我们从一个例子开始:人们从BookStore购买书籍。 我们的BookStore有库存1000书和每一个客户购买一本书,库存的数量被减少一个时间。 这是我们的Buyer类的外观:

class Buyer {    func buy(bookStore: BookStore) {        bookStore.executePurchase()
}}

And this is how our BookStore class may look like:

这就是我们的BookStore类的样子:

class BookStore {    var numberOfBooks = 1000    func executePurchase() {        numberOfBooks -= 1    }
}

Testing BookStore by itself is straightforward. We can do the following:

BookStore测试BookStore很简单。 我们可以执行以下操作:

func testExecutePurchaseDecreasesInventoryByOne() {    // given    let bookStore = BookStore()    // when    bookStore.executePurchase()    // then    XCTAssertEqual(bookStore.numberOfBooks, 999)}

What about testing the Buyer class? If we look at buy(bookStore:) method we will realize that our Buyer class has a dependency on the BookStore class. Remember that we want our test to be focused on the buyer and be less distracted by the implementation details of the book store. We want our test to verify that when buy(bookStore:) is called on any Buyer object, then this causes the inventory of the book store to get decreased by one, but we don’t want to use a real object of the book store in this case. To achieve that, we will create a mock class forBookStore, a class that simulates what happens inside the BookStore but it’s smaller, faster, and easier to change for any future testing needs.

如何测试Buyer类? 如果我们研究buy(bookStore:)方法,我们将意识到我们的Buyer类与BookStore类具有依赖关系。 请记住,我们希望我们的测试针对购买者,而不会因为书店的实施细节而分心。 我们希望我们的测试来验证是否在任何Buyer对象上调用了buy(bookStore:) ,那么这会使书店的库存减少一倍,但是我们不想使用书店的真实对象在这种情况下。 为此,我们将为BookStore创建一个模拟类,该类可模拟BookStore内部发生的情况,但是它更小,更快,更容易更改以适应将来的测试需求。

Let’s see how can we write a test for Buyer using a mocked BookStore instead of using a real instance of BookStore:

让我们看看如何可以写一个测试Buyer使用嘲笑BookStore ,而不是使用的实际实例BookStore

func testBuyerBuyingBookDecreasesBooksInventoryByOne() {    // given    let buyer = Buyer()    let bookStoreMock = BookStoreMock()    // when    buyer.buy(bookStore: bookStoreMock)    // then    XCTAssertEqual(bookStoreMock.numberOfBooks, 999)}

Okay, now we need to create BookStoreMock, a class that will act as a test double for BookStore, meaning that it will be a class that simulates the functionalities that BookStore does but will focus on one aspect. In our case, it will be a class that simulates the book store’s behavior in executing a purchase.

好的,现在我们需要创建BookStoreMock ,该类将充当BookStore的测试BookStore ,这意味着它将是一个模拟BookStore执行功能的类,但将侧重于一个方面。 在我们的例子中,它将是一个类,它模拟书店在执行购买中的行为

The classic computer science book Design Patterns by Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides (The Gang of Four Book) mentions that if we want to define one or more behavior for a class, we should then design to interfaces, not implementations. In Swift, this means using protocols rather than concrete types. Creating a protocol gives us the power to create different classes that conform or agree to the same behaviors but implement them differently. For example, we will create a BookStoreProtocol that declares the method executePurchase, and we will create two classes BookStore and BookStoreMock. Both of these classes will conform to BookStoreProtocol, but inBookStore’s implementation of executePurchase, it will choose to write the numberOfBooks when a purchase happens to a local database, while BookStoreMock's implementation of executePurchase will just return a number and writes something to our logs.

Erich Gamma,Richard Helm,Ralph Johnson和John Vlissides(《四人帮》)的经典计算机科学著作《 设计模式 》提到,如果我们要为一个类定义一个或多个行为,则应该设计接口 ,而不是接口实现 。 在Swift中,这意味着使用协议而不是具体类型。 创建协议使我们能够创建符合或同意相同行为但以不同方式实现它们的不同类。 例如,我们将创建一个BookStoreProtocol声明的方法executePurchase ,我们将创建两个类BookStoreBookStoreMock 。 这两个类都符合BookStoreProtocol ,但是在BookStoreexecutePurchase实现中,它将选择在本地数据库发生购买时写入numberOfBooks ,而BookStoreMockexecutePurchase的实现只会返回一个数字并向其中写入内容。我们的日志。

Using protocols gives classes the freedom to have different implements for the same behaviors defined in one protocol. This makes creating tests doubles that are needed for writing unit tests easier.

使用协议可使类自由地为一种协议中定义的相同行为提供不同的实现。 这使创建单元测试所需的测试加倍变得容易。

Let’s write a protocol that will define the expected behaviors from the BookStore class:

让我们写一个协议来定义BookStore类的预期行为:

protocol BookStoreProtocol {    var numberOfBooks: Int { get set }    func executePurchase()}

Now, let’s make our original BookStore class conform to it:

现在,让我们的原始BookStore类符合它:

class BookStore: BookStoreProtocol {    var numberOfBooks = 1000    func executePurchase() {        numberOfBooks -= 1    }}

and let’s create our mock class for BookStore :

让我们为BookStore创建模拟类:

class BookStoreMock: BookStoreProtocol {    var numberOfBooks = 1000    func executePurchase() {        numberOfBooks -= 1
print("BookStoreMock: The current number of books in the inventory is \(numberOfBooks)") }}

In our simple example, the implementation of executePurchase is not that different if we compare BookStore and BookStoreMock, but in larger projects, mocked methods tend to have a simpler implementation compared to the concrete implementation.

在我们的简单示例中,如果我们比较BookStoreBookStoreMockexecutePurchase的实现没有什么不同,但是在较大的项目中,与具体实现相比,模拟方法的实现往往更简单。

There’s one more change that we need to do, we need to change the buy method inside our Buyer class to accept an argument of our protocol type BookStoreProtocol instead of the concrete type BookStore. This allows us to call buy(bookStore:) in our production code by passing an instance of BookStore and allows us to call buy(bookStore:) in our unit tests by passing an instance of BookStoreMock. The key player here is our protocol BookStoreProtocol that allowed us to do that. This is how the Buyer class will look like after this change:

我们还需要做另一项更改,我们需要更改Buyer类中的buy方法,以接受协议类型BookStoreProtocol而不是具体类型BookStore 。 这允许我们通过传递BookStore的实例在生产代码中调用buy(bookStore:) ,并允许我们通过传递BookStoreMock的实例在单元测试中调用buy(bookStore:) 。 这里的关键参与者是我们的协议BookStoreProtocol ,该协议允许我们执行此操作。 进行此更改后, Buyer类如下所示:

class Buyer {    func buy(bookStore: BookStoreProtocol) {        bookStore.executePurchase()
}}

If you run the unit tests now, they should be green.

如果您现在运行单元测试,则它们应为绿色。

部分模拟与完整模拟 (Partial Mocks vs. Full Mocks)

The approach that we followed in the previous example with our BookStore and BookStoreMock classes is called full or complete mocking because we create completely separate types for our testing purposes. There’s another approach called partial mocking, it involves subclassing a specific type and overriding the parts that we want to simulate in our tests doubles only.

在上一个示例中,我们对BookStoreBookStoreMock类采用的方法称为完全或完全模拟,因为我们出于测试目的创建了完全独立的类型。 还有另一种方法叫做部分模拟,它涉及子类化一个特定的类型,并且覆盖我们想要在测试中模拟的部分,它们只会加倍。

Let’s see how we can implement that. In the scenario of partial mocking, we want our partially mocked class to implement a different behavior for executePurchase method. Instead of decreasing the number of books by 1, we will decrease the number of books by 10 when executePurchase is called. Our unit test should verify that deduction behavior is happening successfully.

让我们看看如何实现它。 在部分executePurchase的情况下,我们希望部分executePurchase类为executePurchase方法实现不同的行为。 当executePurchase时,我们将书籍数量减少10个,而不是将书籍数量减少1个。 我们的单元测试应验证演绎行为是否成功发生。

This is our partially mocked class:

这是我们的部分嘲笑类:

class BookStorePartialMock: BookStore {    override func executePurchase() {        numberOfBooks -= 10    }}

In this case, our buy(bookStore:) method inside our Buyer class can accept an argument of the type BookStore with no problems:

在这种情况下,我们的Buyer类中的buy(bookStore:)方法可以buy(bookStore:)接受BookStore类型的参数:

class Buyer {    func buy(bookStore: BookStore) {        bookStore.executePurchase()    }}

and our unit test now can look like the following:

现在我们的单元测试如下所示:

func testBuyerBuyingBookDecreasesBooksInventoryByOne() {    // given    let buyer = Buyer()    let bookStoreMock = BookStorePartialMock()
// when buyer.buy(bookStore: bookStoreMock) // then XCTAssertEqual(bookStoreMock.numberOfBooks, 990)}

At this point, I hope that I managed to make the mocking concept in Swift a little bit approachable for you. In this article, I shared the basic definition of what a “test double” means and what are the different types of test doubles that we can create. I also talked a little bit about the definition of mocking and why mocking can be useful when we write tests. If you have any questions, suggestions, or improvements I’m always happy to learn more and exchange knowledge with all of the awesome developers everywhere. Please feel free to write a comment, tweet me, or drop me a line!

在这一点上,我希望我设法使Swift中的模拟概念对您来说有点平易近人。 在本文中,我分享了“ 测试倍数 ”的含义以及可以创建的不同类型的测试倍数的基本定义。 我还谈到了模拟的定义,以及为什么在编写测试时模拟可以有用。 如果您有任何问题,建议或改进,我总是很乐意学习更多信息,并与世界各地的所有优秀开发人员交流知识。 请随时发表评论, 鸣叫我给我留言!

翻译自: https://medium.com/swlh/mocking-in-swift-3026b438b56c

swift 引用swift

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值