go的嘲笑技巧

    科技2022-07-12  129

    Unit testing. It’s one of those situations that every engineer faces.

    单元测试。 这是每个工程师都面临的情况之一。

    If you’re dealing with a downstream dependency, you’ll want to keep your unit test fast, self-contained, and reliable.

    如果要处理下游依赖性,则需要保持单元测试快速,独立且可靠。

    It can be daunting when you’re new to a language or framework.

    当您不熟悉某种语言或框架时,这可能会令人生畏。

    We talked with Kyle Yost, a Software Engineer at CB Insights, who’s been working with Go throughout his career. His team emphasizes testing so they can have confidence in the code they ship. Along the way, Kyle found that Go provides all the tools they need to achieve mocking and accomplish their unit tests.

    我们与CB Insights的软件工程师Kyle Yost进行了交谈,他在整个职业生涯中一直与Go合作。 他的团队强调测试,因此他们可以对所发布的代码充满信心。 在此过程中,Kyle发现Go提供了实现模拟和完成单元测试所需的所有工具。

    While third-party solutions were an option, he discovered that Go’s mocking techniques were the best route.

    尽管可以选择第三方解决方案,但他发现Go的模拟技术是最好的方法。

    Third-party tools can make things easier, but Kyle didn’t want to sacrifice his knowledge in the process. He found it worthwhile to understand exactly how the team achieves the unit tests. Staying close to what’s happening, rather than farming it out to a third-party, ensured they didn’t sacrifice their knowledge in the process.

    第三方工具可以使事情变得容易,但是Kyle不想在此过程中牺牲自己的知识。 他发现值得完全了解团队如何完成单元测试。 紧贴正在发生的事情,而不是将其交付给第三方,以确保他们在此过程中不会牺牲自己的知识。

    To make things easier, he created a resource that categorizes different mocking techniques (including the situations that would lead you to use them).

    为了使事情变得容易,他创建了一个资源,该资源对不同的模拟技术进行了分类(包括可能导致您使用它们的情况)。

    The techniques below frame the problem in terms of what’s trying to be achieved or the situation that’s faced. Examples are included with each technique.

    下面的技术根据要实现的目标或面临的情况来构架该问题。 每种技术都包含示例。

    5 Mocking Techniques:

    5种模拟技术:

    Higher-Order Functions

    高阶函数 Monkey Patching

    猴子修补 Interface Substitution

    接口替代 Embedding Interfaces

    嵌入接口 Mocking out Downstream HTTP Calls with net/http/httptest

    使用net / http / httptest模拟出下游HTTP调用

    Higher-Order Functions

    高阶函数

    Use when you need to mock some package level function.

    当您需要模拟某些包级功能时使用。

    Consider this source code that you want to test. It opens a DB connection to mysql.

    考虑您要测试的此源代码。 打开与mysql的数据库连接。

    We want to mock out the call to sql.Open. We can make the following change to the source code to pass in a function to open the connection.

    我们要模拟对sql.Open的调用。 我们可以对源代码进行以下更改,以传递函数来打开连接。

    When calling this function in our source code, we can supply the sql.Open function to it:

    在源代码中调用此函数时,我们可以为其提供sql.Open函数:

    OpenDB(“myUser”, “myPass”, “localhost”, “foo”, sql.Open)

    OpenDB(“ myUser”,“ myPass”,“ localhost”,“ foo”,sql.Open)

    When we are testing the function, we can supply our own definition of the function in each table test. Here is a complete example with one happy path test and one mock error test:

    在测试功能时,我们可以在每个表测试中提供我们自己的功能定义。 这是一个完整的示例,其中包含一个快乐路径测试和一个模拟错误测试:

    Exercise caution when putting this technique to use. HOFs may be difficult to reason about since you are passing in logic that is not proximal to the function body. You may also expand function parameter lists beyond what is reasonable to read. Also consider that you can end up expanding the list of dependencies for packages that call your function. In the example above, callers of OpenDB(…) now need to import the sql package.

    使用此技术时请小心。 HOF可能难以推理,因为您传递的是不接近功能主体的逻辑。 您还可以扩展功能参数列表,使其超出合理范围。 还应考虑到最终可以扩展调用函数的程序包的依赖关系列表。 在上面的示例中,OpenDB(...)的调用者现在需要导入sql包。

    Monkey Patching

    猴子修补

    Use when you need to mock some package level function.

    当您需要模拟某些包级功能时使用。

    This technique is very similar to higher-order functions. We make a package level variable in our source code that points to the real call that we need to mock. Instead of passing in a function to OpenDB(), we just use the variable for the actual call.

    此技术与高阶函数非常相似。 我们在源代码中创建一个包级变量,该变量指向我们需要模拟的真实调用。 无需将函数传递给OpenDB(),我们仅将变量用于实际调用。

    In your test file, you simply reassign the SQLOpen variable in the source code with your mock implementation right before you call the function under test.

    在测试文件中,您只需在调用被测函数之前就通过模拟实现在源代码中重新分配SQLOpen变量。

    Sometimes package level variables may not be the best way to write testable code. You may not be able to run tests in parallel without synchronization when many tests are manipulating a single variable. Similarly, if you are writing tests from a test package ( ex: package mypkg_test ), you will need to make this variable public so that your test package can change it. This would also allow other callers of your package to do the same, which is usually not an intended consequence.

    有时,程序包级别的变量可能不是编写可测试代码的最佳方法。 当许多测试正在操纵一个变量时,您可能无法在没有同步的情况下并行运行测试。 同样,如果要从测试程序包(例如:package mypkg_test)编写测试,则需要将该变量设置为公共变量,以便测试程序包可以更改它。 这也将允许包的其他调用者执行相同的操作,这通常不是预期的结果。

    Use caution with this technique and beware of side effects!

    请谨慎使用此技术,并注意副作用!

    Interface Substitution

    接口替代

    Use when you need to mock a method on a concrete type.

    需要模拟具体类型的方法时使用。

    In Go, interfaces are implicitly and statically satisfied by implementing types. That means you do not need to explicitly mention that your type will “implement” an interface. If it can do the behaviors of the interface, it is allowed to be treated that way. The static satisfaction means you find out at compile time whether or not your concrete type can be substituted as an interface type. This is one distinguishing mark from true “duck typing” that you see in dynamic languages like python. Because of this, interfaces are incredibly powerful for mocking in tests. The following technique follows the “D” from SOLID design pattern considerations — API boundaries should depend on abstractions rather than concrete implementations.

    在Go中,实现类型可以隐式和静态地满足接口。 这意味着您无需明确提及您的类型将“实现”接口。 如果它可以执行界面的行为,则可以采用这种方式进行处理。 静态满足意味着您在编译时会发现您的具体类型是否可以替换为接口类型。 这是您在诸如python之类的动态语言中看到的与真正的“鸭子打字”区别的一个标记。 因此,接口在测试中具有强大的模拟功能。 以下技术遵循SOLID设计模式中的“ D”原则-API边界应取决于抽象而非具体实现。

    Sometimes we need to mock a method defined on a type. The simplest way to do this is to define an interface which describes the behaviors that you need rather than dealing with the concrete type. One example is reading from a file. Maybe we do not want to actually read from a file in our unit test. Consider the code below that opens a file in the main function and then calls another method on the os.File type to read a specified number of bytes and close the file.

    有时我们需要模拟在类型上定义的方法。 最简单的方法是定义一个描述所需行为的接口,而不是处理具体类型。 一个示例是从文件读取。 也许我们不想在单元测试中实际读取文件。 考虑下面的代码,该代码在main函数中打开一个文件,然后在os.File类型上调用另一个方法以读取指定数量的字节并关闭该文件。

    We need to mock out the functionality from the file that is used during ReadContents(…). Specifically, we read from the file with f.Read(data) and we eventually close the file with defer f.Close().

    我们需要从ReadContents(…)期间使用的文件中模拟功能。 具体来说,我们使用f.Read(data)读取文件,最后使用defer f.Close()关闭文件。

    We allow for a mock by accepting interfaces rather than an os.File struct. In the io package in the standard library, there are useful interfaces that we can use:

    我们通过接受接口而不是os.File结构来进行模拟。 在标准库的io包中,可以使用一些有用的接口:

    ReadCloser “embeds” Reader and Closer, meaning that it is satisfied when Reader and Closer are satisfied. More on embedding interfaces in the next technique. We will use io.ReadCloser in our function signature. Note that os.File is still what our source code will supply to the function, and this works even though the call to rc.Read(data) is only using the method that is intended to satisfy io.Reader.

    ReadCloser“嵌入” Reader和Closer,表示满足Reader和Closer时满足。 有关在下一种技术中嵌入接口的更多信息。 我们将在函数签名中使用io.ReadCloser。 请注意,os.File仍然是我们的源代码将提供给该函数的内容,即使对rc.Read(data)的调用仅使用旨在满足io.Reader的方法,该方法仍然有效。

    This follows the pattern to “accept interfaces, return structs” in Go, which allows you to consistently abstract what you need as a caller, rather than a supplier of functionality. An os.File struct is returned from the call to os.Open(), and we may use any of the methods defined on that type. For the specific methods that we need to mock (Read() and Close()) we accept an interface instead of the concrete type in ReadContents(…). In most cases, you may need to create these interfaces yourself, but here we were able to reuse those defined in the io package.

    这遵循Go中“接受接口,返回结构”的模式,它使您能够始终抽象化作为调用者(而不是功能提供者)所需的内容。 从对os.Open()的调用中返回了os.File结构,我们可以使用对该类型定义的任何方法。 对于我们需要模拟的特定方法(Read()和Close()),我们接受一个接口,而不是ReadContents(…)中的具体类型。 在大多数情况下,您可能需要自己创建这些接口,但是在这里,我们能够重用io包中定义的那些接口。

    Now our test for this function can easily be mocked.

    现在我们可以轻松地模拟此功能的测试。

    Notice that the mockReadCloser struct has fields that dictate what the mock will return. This way, each table test can instantiate the struct with its desired return values.

    注意,mockReadCloser结构具有指示模拟将返回什么的字段。 这样,每个表测试都可以使用其所需的返回值实例化该结构。

    Embedding Interfaces

    嵌入接口

    Use when you need to mock out a small set of methods defined in a large interface.

    当您需要模拟在大型接口中定义的一小组方法时使用。

    A great example of this situation comes from the DynamoDB documentation.

    DynamoDB文档就是一个很好的例子。

    When working with the aws-sdk, they provide interfaces for all of their major services that are quite large since they contain all of the calls that can be made for each particular client. Take a look at the dynamodbiface.DynamoDBAPI interface from the link. Rather than pass around the concrete client type, you should pass around this interface to other functions. But then, when testing some of your code that calls one particular function of the interface, how do you mock out that call only without mocking every other function in an attempt to satisfy the interface? Here is the example from the link:

    当使用aws-sdk时,它们为所有大型服务提供接口,因为它们包含可以为每个特定客户端进行的所有调用,因此非常大。 从链接中查看dynamodbiface.DynamoDBAPI接口。 您应该将此接口传递给其他功能,而不是传递具体的客户端类型。 但是,然后,当测试一些调用该接口的一个特定功能的代码时,如何仅模拟该调用而又不模拟其他所有功能来满足该接口呢? 这是链接中的示例:

    Source Code:

    源代码:

    This is an incomplete example for simplicity, but notice that myFunc is signed with the dynamodbiface.DynamoDBAPI interface which contains the entire API for DynamoDB. It will use it only for a call to BatchGetItem, so that is what we need to mock.

    为了简单起见,这是一个不完整的示例,但是请注意,myFunc已使用dynamodbiface.DynamoDBAPI接口进行了签名,该接口包含DynamoDB的整个API。 它只会将其用于对BatchGetItem的调用,因此我们需要对此进行模拟。

    Test:

    测试:

    So instead of having to create our own type that satisfies the entire interface, we can simply embed the dynamodbiface.DynamoDBAPI inside our mock struct (to implicitly satisfy the interface contract) and then redefine the function(s) that we care about.

    因此,不必创建自己的类型来满足整个接口,我们只需将dynamodbiface.DynamoDBAPI嵌入到我们的模拟结构中(以隐式满足接口协定),然后重新定义我们关心的功能即可。

    Mocking out Downstream HTTP Calls with net/http/httptest

    使用 net / http / httptest模拟出下游HTTP调用

    Use when your code under test makes an HTTP call to a downstream service.

    当您的被测代码对下游服务进行HTTP调用时使用。

    It is generally understood that unit tests should not connect to external services in order to remain reliable and self-contained. Any one of the previous mocking techniques would suffice for this situation (depending on the construction of your code), but the standard library provides a better way to achieve this. The net/http/httptest package provides a Server type that will listen on your system’s local loopback interface. This is a server completely self-contained within your system’s network, so no external network calls are made, but you can still get the benefit of exercising code that is very similar to the actual calls that your source code will make. To swap out the actual server for a test server during your test, simply parameterize the URL that you will be connecting to, and then call your function under test with the URL of the test server.

    通常可以理解,单元测试不应连接到外部服务,以保持可靠和独立。 以前的任何一种模拟技术都可以满足这种情况(取决于代码的构造),但是标准库提供了一种更好的方法来实现此目的。 net / http / httptest软件包提供了一种Server类型,它将在系统的本地环回接口上进行侦听。 这是一台完全独立于系统网络中的服务器,因此不会进行外部网络调用,但是您仍然可以从执行与源代码实际调用非常相似的代码中受益。 要在测试过程中将实际服务器换成测试服务器,只需将要连接的URL参数化,然后使用测试服务器的URL调用被测函数。

    Consider this function, which makes an HTTP call and returns a struct containing the data from the body of the response:

    考虑一下此函数,该函数进行HTTP调用并返回一个结构,该结构包含响应正文中的数据:

    Test:

    测试:

    Here we let every test case in our test table to create and close a test server with a mocked response. Since the call to httptest.NewServer() takes in an http.Handler, you may decide to just create one test server for all of your test cases, but with different routes, logic, or custom responses.

    在这里,我们让测试表中的每个测试用例创建并关闭带有模拟响应的测试服务器。 由于对httptest.NewServer()的调用包含一个http.Handler,因此您可以决定只为所有测试用例创建一个测试服务器,但是具有不同的路由,逻辑或自定义响应。

    Summary

    摘要

    Without lecturing on the importance of keeping unit tests reliable and self-contained, this article can hopefully serve as a reference for the many situations you may find yourself in while writing tests in Go. Tests only add value if you have complete confidence in your approach. The main goal of automated testing should be to give you confidence in the code that you are shipping. Any mystery introduced by a third party package works counter to that goal. If other packages are perfectly understood and they make your life easier, then go for it!

    无需讲授保持单元测试可靠和独立的重要性,本文有望为您在Go中编写测试时可能遇到的许多情况提供参考。 只有对方法完全有信心时,测试才能增加价值。 自动化测试的主要目标应该是使您对所交付的代码充满信心。 第三方程序包引入的任何奥秘都与该目标背道而驰。 如果完全理解其他软件包,并且它们使您的生活更轻松,那就去吧!

    Don’t accept that any code is “untestable”, and keep a tight grip on your tests!

    不要接受任何代码都是“不可修改的”,并紧紧抓住您的测试!

    Originally published at https://dmv.myhatchpad.com.

    最初发布在 https://dmv.myhatchpad.com 。

    翻译自: https://medium.com/hatchpad/mocking-techniques-for-go-f0a302457d30

    相关资源:微信小程序源码-合集6.rar
    Processed: 0.011, SQL: 8