GO语言(十六):模糊测试入门(上)

Python018

GO语言(十六):模糊测试入门(上),第1张

本教程介绍了 Go 中模糊测试的基础知识。通过模糊测试,随机数据会针对您的测试运行,以尝试找出漏洞或导致崩溃的输入。可以通过模糊测试发现的一些漏洞示例包括 SQL 注入、缓冲区溢出、拒绝服务和跨站点脚本攻击。

在本教程中,您将为一个简单的函数编写一个模糊测试,运行 go 命令,并调试和修复代码中的问题。

首先,为您要编写的代码创建一个文件夹。

1、打开命令提示符并切换到您的主目录。

在 Linux 或 Mac 上:

在 Windows 上:

2、在命令提示符下,为您的代码创建一个名为 fuzz 的目录。

3、创建一个模块来保存您的代码。

运行go mod init命令,为其提供新代码的模块路径。

接下来,您将添加一些简单的代码来反转字符串,稍后我们将对其进行模糊测试。

在此步骤中,您将添加一个函数来反转字符串。

a.使用您的文本编辑器,在 fuzz 目录中创建一个名为 main.go 的文件。

独立程序(与库相反)始终位于 package 中main。

此函数将接受string,使用byte进行循环 ,并在最后返回反转的字符串。

此函数将运行一些Reverse操作,然后将输出打印到命令行。这有助于查看运行中的代码,并可能有助于调试。

e.该main函数使用 fmt 包,因此您需要导入它。

第一行代码应如下所示:

从包含 main.go 的目录中的命令行,运行代码。

可以看到原来的字符串,反转它的结果,然后再反转它的结果,就相当于原来的了。

现在代码正在运行,是时候测试它了。

在这一步中,您将为Reverse函数编写一个基本的单元测试。

a.使用您的文本编辑器,在 fuzz 目录中创建一个名为 reverse_test.go 的文件。

b.将以下代码粘贴到 reverse_test.go 中。

这个简单的测试将断言列出的输入字符串将被正确反转。

使用运行单元测试go test

接下来,您将单元测试更改为模糊测试。

单元测试有局限性,即每个输入都必须由开发人员添加到测试中。模糊测试的一个好处是它可以为您的代码提供输入,并且可以识别您提出的测试用例没有达到的边缘用例。

在本节中,您将单元测试转换为模糊测试,这样您就可以用更少的工作生成更多的输入!

请注意,您可以将单元测试、基准测试和模糊测试保存在同一个 *_test.go 文件中,但对于本示例,您将单元测试转换为模糊测试。

在您的文本编辑器中,将 reverse_test.go 中的单元测试替换为以下模糊测试。

Fuzzing 也有一些限制。在您的单元测试中,您可以预测Reverse函数的预期输出,并验证实际输出是否满足这些预期。

例如,在测试用例Reverse("Hello, world")中,单元测试将返回指定为"dlrow ,olleH".

模糊测试时,您无法预测预期输出,因为您无法控制输入。

但是,Reverse您可以在模糊测试中验证函数的一些属性。在这个模糊测试中检查的两个属性是:

(1)将字符串反转两次保留原始值

(2)反转的字符串将其状态保留为有效的 UTF-8。

注意单元测试和模糊测试之间的语法差异:

(3)确保新包unicode/utf8已导入。

随着单元测试转换为模糊测试,是时候再次运行测试了。

a.在不进行模糊测试的情况下运行模糊测试,以确保种子输入通过。

如果您在该文件中有其他测试,您也可以运行go test -run=FuzzReverse,并且您只想运行模糊测试。

b.运行FuzzReverse模糊测试,查看是否有任何随机生成的字符串输入会导致失败。这是使用go test新标志-fuzz执行的。

模糊测试时发生故障,导致问题的输入被写入将在下次运行的种子语料库文件中go test,即使没有-fuzz标志也是如此。要查看导致失败的输入,请在文本编辑器中打开写入 testdata/fuzz/FuzzReverse 目录的语料库文件。您的种子语料库文件可能包含不同的字符串,但格式相同。

语料库文件的第一行表示编码版本。以下每一行代表构成语料库条目的每种类型的值。由于 fuzz target 只需要 1 个输入,因此版本之后只有 1 个值。

c.运行没有-fuzz标志的go test; 新的失败种子语料库条目将被使用:

由于我们的测试失败,是时候调试了。

Go语言模板文件可以引入js文件或css文件,但是在引入的过程中,需要注意以下几点:

1. 引入的文件路径应该是相对路径,而不是绝对路径。

2. 在引入js文件时,需要使用{{ url }} 模板函数,用来拼接路径, 这样可以更好的兼容不同的路径。

3. 如果是在统一的文件夹中的js文件,最好使用{{ static }}模板函数,这样可以更好的节省路径长度。

4. 在引用js文件时,需要在页面底部,可以使用{{ template }}模板函数,这样可以保证js文件在页面加载完成之前就被加载。

总之,使用Go语言模板文件引入js文件,需要注意路径的相对性,并且使用模板函数来拼接路径,这样可以更好的兼容不同的路径,从而保证引用js文件的正确性。

原文链接: https://blog.drewolson.org/dependency-injection-in-go

github: https://github.com/uber-go/dig

Dependency Injection is the idea that your components (usually structs in go) should receive their dependencies when being created. This runs counter to the associated anti-pattern of components building their own dependencies during initialization. Let’s look at an example.

Suppose you have a Server struct that requires a Config struct to implement its behavior. One way to do this would be for the Server to build its own Config during initialization.

This seems convenient. Our caller doesn’t have to be aware that our Server even needs access to Config . This is all hidden from the user of our function.

However, there are some disadvantages. First of all, if we want to change the way our Config is built, we’ll have to change all the places that call the building code. Suppose, for example, our buildMyConfigSomehow function now needs an argument. Every call site would need access to that argument and would need to pass it into the building function.

Also, it gets really tricky to mock the behavior of our Config . We’ll somehow have to reach inside of our New function to monkey with the creation of Config .

Here’s the DI way to do it:

Now the creation of our Server is decoupled from the creation of the Config . We can use whatever logic we want to create the Config and then pass the resulting data to our New function.

Furthermore, if Config is an interface, this gives us an easy route to mocking. We can pass anything we want into New as long as it implements our interface. This makes testing our Server with mock implementations of Config simple.

The main downside is that it’s a pain to have to manually create the Config before we can create the Server . We’ve created a dependency graph here – we must create our Config first because of Server depends on it. In real applications these dependency graphs can become very large and this leads to complicated logic for building all of the components your application needs to do its job.

This is where DI frameworks can help. A DI framework generally provides two pieces of functionality:

A DI framework generally builds a graph based on the “providers” you tell it about and determines how to build your objects. This is very hard to understand in the abstract, so let’s walk through a moderately-sized example.

We’re going to be reviewing the code for an HTTP server that delivers a JSON response when a client makes a GET request to /people . We’ll review the code piece by piece. For simplicity sake, it all lives in the same package ( main ). Please don’t do this in real Go applications. Full code for this example can be found here .

First, let’s look at our Person struct. It has no behavior save for some JSON tags.

A Person has an Id , Name and Age . That’s it.

Next let’s look at our Config . Similar to Person , it has no dependencies. Unlike Person , we will provide a constructor.

Enabled tells us if our application should return real data. DatabasePath tells us where our database lives (we’re using sqlite). Port tells us the port on which we’ll be running our server.

Here’s the function we’ll use to open our database connection. It relies on our Config and returns a *sql.DB .

Next we’ll look at our PersonRepository . This struct will be responsible for fetching people from our database and deserializing those database results into proper Person structs.

PersonRepository requires a database connection to be built. It exposes a single function called FindAll that uses our database connection to return a list of Person structs representing the data in our database.

To provide a layer between our HTTP server and the PersonRepository , we’ll create a PersonService .

Our PersonService relies on both the Config and the PersonRepository . It exposes a function called FindAll that conditionally calls the PersonRepository if the application is enabled.

Finally, we’ve got our Server . This is responsible for running an HTTP server and delegating the appropriate requests to our PersonService .

The Server is dependent on the PersonService and the Config .

Ok, we know all the components of our system. Now how the hell do we actually initialize them and start our system?

First, let’s write our main() function the old fashioned way.

First, we create our Config . Then, using the Config , we create our database connection. From there we can create our PersonRepository which allows us to create our PersonService . Finally, we can use this to create our Server and run it.

Phew, that was complicated. Worse, as our application becomes more complicated, our main will continue to grow in complexity. Every time we add a new dependency to any of our components, we’ll have to reflect that dependency with ordering and logic in the main function to build that component.

As you might have guessed, a Dependency Injection framework can help us solve this problem. Let’s examine how.

The term “container” is often used in DI frameworks to describe the thing into which you add “providers” and out of which you ask for fully-build objects. The dig library gives us the Provide function for adding providers and the Invoke function for retrieving fully-built objects out of the container.

First, we build a new container.

Now we can add new providers. To do so, we call the Provide function on the container. It takes a single argument: a function. This function can have any number of arguments (representing the dependencies of the component to be created) and one or two return values (representing the component that the function provides and optionally an error).

The above code says “I provide a Config type to the container. In order to build it, I don’t need anything else.” Now that we’ve shown the container how to build a Config type, we can use this to build other types.

This code says “I provide a *sql.DB type to the container. In order to build it, I need a Config . I may also optionally return an error.”

In both of these cases, we’re being more verbose than necessary. Because we already have NewConfig and ConnectDatabase functions defined, we can use them directly as providers for the container.

Now, we can ask the container to give us a fully-built component for any of the types we’ve provided. We do so using the Invoke function. The Invoke function takes a single argument – a function with any number of arguments. The arguments to the function are the types we’d like the container to build for us.

The container does some really smart stuff. Here’s what happens:

That’s a lot of work the container is doing for us. In fact, it’s doing even more. The container is smart enough to build one, and only one, instance of each type provided. That means we’ll never accidentally create a second database connection if we’re using it in multiple places (say multiple repositories).

Now that we know how the dig container works, let’s use it to build a better main.

The only thing we haven’t seen before here is the error return value from Invoke . If any provider used by Invoke returns an error, our call to Invoke will halt and that error will be returned.

Even though this example is small, it should be easy to see some of the benefits of this approach over our “standard” main. These benefits become even more obvious as our application grows larger.

One of the most important benefits is the decoupling of the creation of our components from the creation of their dependencies. Say, for example, that our PersonRepository now needs access to the Config . All we have to do is change our NewPersonRepository constructor to include the Config as an argument. Nothing else in our code changes.

Other large benefits are lack of global state, lack of calls to init (dependencies are created lazily when needed and only created once, obviating the need for error-prone init setup) and ease of testing for individual components. Imagine creating your container in your tests and asking for a fully-build object to test. Or, create an object with mock implementations of all dependencies. All of these are much easier with the DI approach.

I believe Dependency Injection helps build more robust and testable applications. This is especially true as these applications grow in size. Go is well suited to building large applications and has a great DI tool in dig . I believe the Go community should embrace DI and use it in far more applications.