基于GO的自动化测试

1.基于GO的自动化测试

       任何人都可以学习如何组合一个适用于一个版本的信息系统,但是构建一个在多个版本和多年内持续可靠运行的系统不仅需要学习一个好的架构,还需要学习自动化测试的学科。事实上,将专业开发人员与业余开发人员区分开来的最大因素是致力于为其功能编写自动化测试。

随着项目规模和复杂性的增长,一套自动化测试将有助于确保小错误修复或为新功能所做的更改不会破坏应用软件现有的功能。如果你的测试已完成(即,它们通过测试成功和失败案例来测试应用软件的所有代码路径),你就可以放心地进行更改并发布新版本。

如果你做对了,你的测试代码实际上会比你的功能代码更长。例如,SQLite 库的自动化测试代码是功能代码的 730 倍。他们可以修复错误或更改他们最核心的代码,但在相对较短的时间内知道他们的用户所依赖的功能仍然按预期运行。在发布新版本之前,他们使用多种构建配置在多个平台上运行所有测试。手工测试往往是根据个人经验进行,手动进行所有测试是不可想象的。

2.什么是自动化测试?

       自动化测试只是另一个程序,它调用您的功能代码、传递各种输入并测试输出以确保它们符合您的预期。这些程序应该测试有效和无效的输入,如果可能,触发异常错误条件以确保功能代码正确处理它们。

理想情况下,您的测试应该在某个时候调用您的功能代码的每一行。调用的行的百分比称为您的代码覆盖率,它应该尽可能高。在许多情况下,您可以达到 100%,但在某些情况下,只有在出现您的测试无法制造的非常意外的错误时才会运行代码路径。

您可以用任何语言编写自动化测试,如Java,python等,有些语言提供了专门的工具或环境来编写和运行这些测试。例如,Go 定义了一种编写自动化测试的方法,这些测试会自动从已编译的可执行文件中排除,但可以在开发时使用他们的 go 测试工具运行。此工具还提供代码覆盖率分析,准确显示您的测试执行了哪些行,哪些没有执行。

简单的说,就是用程序 测试应用程序,并且出测试报告。

3.测试级别

我们可以在多个级别编写自动化测试:独立的单个函数或类;集成组件组;或整个系统像在生产中一样运行


3.1 Unit Tests(单元测试)

最常见的自动化测试是那些单独测试功能或类的测试。这些被称为单元测试,因为它们一次只测试系统的一个单元。这些测试应尽可能详尽,并涵盖所有代码路径。

这些测试通常以数据驱动的风格编写:您定义多组输入参数和预期输出,然后迭代测试每个输入/输出对。当您发现新的可能输入组合时,您只需将它们添加到列表中,测试代码就会自动测试它们。请参阅下面的 Go 示例。

3.2 Integration Tests(集成测试)

一旦您知道您的系统单元是独立工作的,您就可以测试它们在与其他单元集成到子系统中时的行为方式。这也是您测试自己的单元如何与其他人编写的代码交互的地方:例如,可重用库、数据库管理系统或操作系统。这些被称为集成测试,因为您正在测试您的代码单元如何与其他代码集成。

集成测试不是详尽地测试输入和输出,而是更多地测试单元如何通过事务流进行交互。例如,您编写的用于管理 DMBS(用户存储)中的用户帐户的代码模块将公开多个功能,您将在单元测试中单独测试这些功能,但在集成测试中您将确保这些功能正常工作在典型的创建/读取/更新/删除 (CRUD) 循环中正确地组合在一起。您的集成测试将执行以下操作:

1)创建新记录

2)读取记录并确保正确保存

3)更新记录

4)再次阅读以确保它确实已更新

5)删除它

6)确保您无法再阅读它

集成测试验证单元之间的依赖关系,从而可以揭示单元测试遗漏的错误。例如,如果您将本地缓存添加到您的用户存储以提高性能,但在更新用户记录时忘记使缓存条目失效或修补,您的集成测试将捕获它,而您的单元测试可能不会。

要运行这样的测试,您自然必须与 DBMS 对话,但在许多语言中,您可以创建 DBMS 的模拟实现,以便您的测试更易于运行。模拟实现与 DBMS 客户端库相同的接口,但使用简单的内存数据存储,而不是与实际的数据库服务器对话。使用模拟时,开发人员不需要在本地运行 DBMS 来运行测试,并且您的测试不受 DBMS 中可能已经存在的数据的影响。 Mock 还可以更轻松地触发使用实际 DBMS 时可能无法触发的异常运行时错误,因此它们可以帮助您实现更高的代码覆盖率。

3.3 System Tests(系统测试)

单元测试和集成测试一次只关注系统的一个部分,但系统测试旨在在类似于生产的环境中测试整个系统。例如,Web 服务器处理程序函数的单元测试将直接调用该函数,但 Web 服务器的系统测试实际上会启动服务器及其依赖项(临时和持久 DBMS、消息队列等),然后向服务器发送真实的 HTTP 请求,评估 HTTP 响应。

系统测试可以发现仅在生产环境中才会出现的问题。例如,系统测试可能会发现您与 DBMS 的连接由于网络故障而丢失时发生的问题。您的系统是重新建立连接,还是在重新启动之前继续失败?

系统测试还可以验证系统级功能,例如当主 DBMS 出现故障时自动故障转移到备份 DBMS

3.4 Stress Tests(压力测试,性能测试)

The final type of automated tests to mention are stress tests. These are a bit different than the previous types of tests as they are not really about testing features, per se. Instead they are about testing how your system behaves under stressful conditions, such as a very high transaction load. Stress tests will determine how much your system can handle, and at what point it will fail without more resources.

Stress tests require a bit more in terms of test infrastructure. In order to mimic a high transaction load on a web server, you really need to have dozens of clients all making multiple requests to the server at the same time. Each client measures how long each request took to process, and the server monitors things like request queue size, CPU/memory usage, and DBMS query duration.

Once you build a stress test infrastructure, you can run several tests with different system configurations and actually measure the results to see which configuration gives you the scalability you need for the least cost. Without this, you're just guessing and hoping.

3.5 用go编写例子

Reverse 返回作为 `s` 传递的字符串的反向,将字符串转换为切片,以便我们可以操作它,由于字符串是不可变的,这将创建一个副本字符串,所以我们不会修改原来的。


如果您在 Go 语言教程中一直在关注,您可能会在上面的代码中发现一个错误。如果没有,请不要担心:我们的自动化测试很快就会发现它。

在同一目录中创建另一个名为 reverse_test.go 的文件。 Go 将以 _test 结尾的文件视为自动化测试文件,因此它不会将这些文件编译到您构建的可执行文件中,但会在您调用 go test 工具时运行其中的测试。

由于我们正在为 Reverse() 函数编写单元测试,因此我们将使测试数据驱动。我们将指定几对输入字符串和预期的输出字符串,并测试每个案例以确保我们得到预期的结果。这些情况不仅应包括常见情况,还应包括意外情况,例如输入的空字符串(Go 编译器不允许将 nil 传递给字符串参数,因此我们不必担心)。

所有测试函数都必须以单词 Test 开头,并且通常在后面加上您正在测试的函数的名称。测试函数还必须只接受一个 *testing.T 类型的参数,通常命名为 t。此参数使您可以访问诸如 t.Errorf() 之类的函数,这些函数可让您报告错误。如果调用 t 参数上的任何错误方法,则测试失败。

函数顶部看起来很疯狂的语法是声明和初始化一个匿名结构体,每个结构体都有两个字符串字段:input 和 expectedOutput。匿名结构就像匿名函数——它们被声明为没有名称的内联。我们可以声明一个单独的结构类型,然后像这样引用它:

type testcase struct { 

     input string expected

     Output string

}

cases := []testcase{ /* initializer */ }

但是由于我们永远不需要在函数中的任何其他地方引用该结构类型,因此将其内联声明为匿名结构会更简单。

匿名结构体定义后面的初始化表达式用几个输入和预期输出对填充结构体切片。我们从空字符串的意外情况开始,这应该导致另一个空字符串。然后我们尝试各种字符串长度以确保我们的算法适用于偶数和奇数长度。最后,我们测试一个回文,这是一个向前和向后拼写相同的单词。

在声明并初始化结构体切片后,我们只需遍历切片,将每个结构体的输入字段传递给我们的 Reverse() 函数,并验证输出是否与 expectedOutput 字段匹配。如果它们不匹配,我们将使用 t.Errorf() 报告错误。

您传递给 t.Errorf() 的消息应该为开发人员提供足够的信息,以了解哪个案例失败以及为什么失败。请记住,由于其他开发人员不小心修复了错误,测试可能会在您编写数年后失败,因此请尽可能描述。

要运行此测试,请转到终端窗口,确保您位于包含 reverse.go 和 reverse_test.go 文件的目录中,然后执行以下命令:go test


4.创建一个集成测试

上面的例子是一个单元测试,所以我们把它写成数据驱动的和详尽的。如前所述,集成测试更多的是测试一系列调用,例如典型的 CRUD 循环,以确保它们一起正常工作。如果这些调用与 DBMS 等外部组件交互,我们通常会尝试使用 DBMS 客户端库的模拟实现,以便我们的测试不需要 DBMS 的运行实例。

Go 的静态类型使得构建模拟变得困难,除非 DBMS 客户端库将其 API 定义为 Go 接口。接口是一种可以由多个包中的多个结构实现的类型,包括我们构建和用于自动化测试的模拟实现。

不幸的是,大多数 DBMS 客户端包并没有将它们的 API 定义为接口。相反,它们只是定义了结构和函数,不能通过模拟透明地实现。这是一个架构缺陷,我希望能在这些软件包的未来版本中得到修复,但现在我们必须处理它。

一种方法是定义您自己的接口来封装模型对象的“存储”。该接口将定义用于在数据存储中查找、插入、更新和删除模型的各种功能。例如,用于管理“消息”存储的界面可能如下所示:

package "messages"

type Store interface { 

 func Insert(newMessage *NewMessage) (*Message, error) 

 func Get(id bson.ObjectId) (*Message, error) 

 func Find(q *Query) ([]*Message, error) 

 func Update(message *Message) error 

 func Delete(id bson.ObjectId) error

}

可以简单了解一下interface接口定义

运行后的结果:

{3 4}

12

14

{5}

78.53981633974483

31.41592653589793

5.测试驱动和行为驱动的开发 TDD

认真对待自动化测试的组织通常会实践所谓的测试驱动开发 (TDD)。使用这种方法,开发人员首先根据功能规范编写他们的自动化测试,然后通过仅添加使测试通过所需的代码来实现他们的功能。这与通常的做法相反,但 TDD 的支持者认为,在特性之后实施测试往往会产生两个不利影响:

1)开发人员在实现功能时常常会不知所措,添加比规范要求更多的功能和选项,这只会为错误和安全漏洞创造更多机会。往往如果增加这个,代码的工期会加长,需要和开发,以及项目经理,讨论出一个合理范围才可以做好。

2)由于编写软件通常需要比预期更长的时间,特别是如果开发人员添加了不必要的功能,等到最后编写测试会导致测试仓促、薄弱和不完整。测试可以提前接入。但还是避免不了,需求不断变更造成测试用例失效的情况。

TDD 方法导致更快的开发周期和更可靠的软件。这使组织能够更快地响应快速变化的环境,而不会在此过程中破坏其系统的稳定性。

TDD 在扩充现有系统时比构建全新系统更有效。要首先编写测试,编写人员最好是测试开发人员必须知道各种结构和函数的外观,但是对于新系统,您可能需要编写一些功能代码来发现哪些有效,哪些无效。

遵循 TDD 流程的组织通常也订阅行为驱动开发 (BDD),它试图将需求和功能规范中使用的语言带入自动化测试中。 BDD 测试框架允许您编写看起来几乎像英语句子的测试:

describe("reverse", function() {

it("should reverse strings", function() {

expect(reverse("hello")).to.equal("olleh");

    });

    it("should handle high Unicode characters", function() {

expect(reverse("Hello, 世界")).to.equal("界世 ,olleH");

    });

    it("should return an empty string for an empty string", function() {

expect(reverse("")).to.equal("");

    });

    it("should return null for null", function() {

expect(reverse(null)).to.be.null;

    });

    //etc...

});

或者可以用Ginkgo 

5.1 什么是Ginkgo?

Ginkgo 是一个 Go 测试框架,旨在帮助您使用行为驱动开发(“BDD”)风格高效地编写富有表现力和全面的测试。它最好与 Gomega 匹配器库配对,但设计为与匹配器无关。

这些文档是假设您将 Gomega 与 Ginkgo 一起使用而编写的。他们还假设你了解围棋的方法,并且对围棋如何在 $GOPATH 下组织包有一个很好的心理模型。感兴趣的可以去了解一下;

6.持续集成测试

所有这些自动化测试都很棒,但前提是您记得运行它们!理想情况下,团队中的每个开发人员都应该在向 repo 提交更改之前运行全套自动化测试,但我们都知道他们有时会忘记。即使所有测试都在开发人员的机器上通过,开发人员有时会忘记提交他们所有的新文件,因此一旦他们拉动其他人的机器,测试就会失败。

为了捕捉这些错误,许多开发团队使用持续集成 (CI) 服务器在每次将提交推送到中央存储库时运行完整的测试套件。这些服务器在后台持续运行,监视您的存储库。每次推送新提交时,服务器都会创建一个新的 repo 克隆,运行所有测试,并通过电子邮件或其他一些通信渠道报告结果。这些系统中的大多数都会启动虚拟机或容器,在其中执行测试,以便它们可以在多个平台和隔离环境中运行测试。

GitHub 上托管的开源项目的流行 CI 服务器是 Travis CI。它对您的所有公共存储库都是免费的,并且他们为拥有私人存储库的学生提供免费的教育帐户。 Travis 只需要在您的 repo 中添加一个额外的 .travis.yml 文件,其中包含有关您的项目和测试的一些信息。这些文件可能非常少,尤其是对于像 Go 这样具有内置测试工具的语言。例如,一个简单的 Go 项目的 .travis.yml 文件可能像这样简单

.travis.yml

language: gogo:

    - 1.x

这告诉 Travis 你的代码是用 Go 编写的,它应该使用最新版本的 Go 工具和标准库来测试它。您可以将其他特定版本的 Go 添加到列表中,以便在这些版本下进行测试。

Travis 支持所有常见的 Web 开发语言,并为您的项目提供令人眼花缭乱的配置选项。它还可以轻松地将流行的 DBMS 添加到环境中,以便您可以运行需要 DBMS 实例的集成测试。

另一个您可以自行下载和托管的强大系统是 Jet Brains 的 Team City,这些人为您带来了 IntelliJ、WebStorm 和 Gogland。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 201,312评论 5 473
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,578评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 148,337评论 0 333
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,134评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,161评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,303评论 1 280
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,761评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,421评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,609评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,450评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,504评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,194评论 3 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,760评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,836评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,066评论 1 257
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,612评论 2 348
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,178评论 2 341

推荐阅读更多精彩内容