以sql-dumper为例在Go中进行测试以达到100%代码覆盖率的方式

图片


在这篇文章中,我将讨论如何用Go语言编写一个控制台程序,该程序用于将数据从数据库上传到文件,并试图通过100%测试覆盖整个代码。 我将从描述为什么需要此程序开始。 我将继续描述第一个困难,其中一些是由Go语言的功能引起的。 接下来,我将介绍在Travis CI上进行的一些构建,然后再讨论如何编写测试,以覆盖100%的代码。 我将稍微测试一下数据库和文件系统的工作。 总而言之,我要说一下用测试覆盖代码的愿望以及该指标的含义。 我将提供包含文档链接和项目提交示例的材料。


计划目的


该程序应从命令行启动,并带有表列表及其某些列的指示,指定的第一列的数据范围,所选表之间相互关系的枚举以及具有使用数据库连接设置指定文件的能力。 工作的结果应该是一个文件,该文件描述创建具有指定列和所选数据的插入表达式的指定表的请求。 假定使用这种程序将简化从大型数据库提取一部分数据并在本地部署这部分数据的方案。 此外,这些卸载的sql文件应该由另一个程序处理,该程序将根据特定模板替换部分数据。


使用数据库的任何流行客户端和足够多的手动工作都可以实现相同的结果。 该应用程序应该简化此过程并尽可能地自动化。


该程序应该由我的实习生开发,目的是培训和随后在他们的进一步培训中使用。 但是事实证明,他们拒绝了这个想法。 但是我决定尝试在业余时间编写这样的程序,以实现使用Go语言进行开发的目的。


解决方案是不完整的;它有许多限制,请参见自述文件。 无论如何,这不是战斗项目。


使用示例和源代码


最初的困难


表及其列的列表作为字符串形式的参数传递给程序,即事先未知。 在Go上使用数据库的大多数示例都暗示该数据库结构是预先已知的,我们只需创建一个指示每列类型的struct即可。 但是,在这种情况下,效果并不理想。


解决方案是使用MapScan方法,该方法创建了一个大小等于示例列数的接口切片。 下一个问题是如何从这些接口获取真实的数据类型。 解决方案是按类型切换 。 这样的解决方案看起来不是很漂亮,因为所有类型都需要转换为字符串:按原样是整数,要转义的字符串并用引号引起来,但同时还要描述可能来自数据库的所有类型。 我没有找到解决此问题的更优雅的方法。


对于这些类型,还显示了Go语言功能-字符串类型的变量不能采用值nil ,但是空字符串和NULL都可以来自数据库。 为了解决这个问题, database/sql包中有一个解决方案 -使用特殊的strut ,无论值是否为NULL ,它都存储值和符号。


通过测试汇编和计算代码覆盖率的百分比


对于汇编,我使用Travis CI,以获取测试覆盖代码的百分比-Coveralls。 程序集的.travis.yml文件非常简单:


 language: go go: - 1.9 script: - go get -t -v ./... - go get golang.org/x/tools/cmd/cover - go get github.com/mattn/goveralls - go test -v -covermode=count -coverprofile=coverage.out ./... - $HOME/gopath/bin/goveralls -coverprofile=coverage.out -service=travis-ci -repotoken $COVERALLS_TOKEN 

在Travis CI设置中,您只需要指定环境变量COVERALLS_TOKEN ,其值必须在site上获取


Coveralls使您可以方便地找出整个项目的百分比,对于每个文件,突出显示一行原始代码,结果证明这是一个未发现的测试。 例如,在第一个版本中 ,很明显在解析用户请求时我没有针对某些错误情况编写测试。


100%的代码覆盖率意味着编写了测试,其中包括在if为每个分支执行代码。 在编写测试以及通常在开发应用程序时,这是最繁琐的工作。


您可以使用本地测试来计算覆盖率,例如,使用相同的go test -v -covermode=count -coverprofile=coverage.out ./... ,但是您可以在CI中做得更好,可以在Github上放置一个盘子。


由于我们在谈论骰子,因此我发现https://goreportcard.com的骰子很有用,它可以分析以下指标:


  • gofmt-代码格式设置,包括简化结构
  • go_vet-检查可疑结构
  • gocyclo-显示圈复杂度的问题
  • golint-对我来说,它正在检查所有必要评论的可用性
  • 许可证-项目必须具有许可证
  • ineffassign-检查无效的作业
  • 拼写错误-检查拼写错误

测试覆盖代码的难度为100%


如果解析用户对组件的小的请求主要是将字符串从字符串转换为某些结构,并且很容易被测试覆盖,那么对于测试与数据库兼容的代码,解决方案就不是那么明显了。


或者,连接到真实的数据库服务器,在每个测试中预先填充数据,进行选择并清除。 但这是一个困难的解决方案,远未进行单元测试,并且对环境(包括CI服务器)提出了要求。


另一种选择是使用内存中的数据库,例如sqlite( sqlx.Open("sqlite3", ":memory:") ),但这意味着代码应尽可能弱地绑定到数据库引擎,这会使项目变得非常复杂但是对于集成测试来说还是相当不错的。


对于单元测试,对数据库使用模拟是合适的。 我找到了这个 。 使用此软件包,您可以在正常结果的情况下和在错误的情况下测试行为,以指示哪个请求应返回哪个错误。


编写测试表明,连接到实际数据库的函数需要移至main.go,因此可以在测试中为将返回模拟实例的函数重新定义该函数。


除了使用数据库之外,还必须使使用文件系统的工作成为单独的依赖项。 这将允许通过写入内存来替换实际文件的记录,以简化测试并减少耦合。 这就是FileWriter界面的FileWriter以及返回的文件界面的外观。 为了测试错误情况,创建了这些接口的辅助实现并将其放置在filewriter_test.go文件中,因此它们不属于常规版本,但可以在测试中使用。


一段时间后,我有一个问题,如何main()测试覆盖main() 。 那时,我在那里有足够的代码。 如搜索结果所示,这不是Go中完成的。 相反,需要从main()提取的所有代码都必须被提取。 在我的代码中,我只剩下解析选项和命令行参数( flag包),连接到数据库,实例化将写入文件的对象,并调用将完成其余工作的方法。 但是这些行不允许您获得完全100%的覆盖率。


在测试Go中,存在诸如“ 示例函数 ”之类的东西。 这些是测试功能,可将输出与该功能内部的注释中所描述的进行比较。 可以在go包源代码中找到此类测试的示例。 如果此类文件不包含测试和基准,那么它们将以前缀example_命名,并以_test.go 。 每个此类测试函数的名称应以Example开头。 在此基础上,我编写了一个针对将sql写入文件的对象的测试,并用模拟代替了文件中的真实记录,您可以从中获取内容并显示它们。 将该结论与标准进行比较。 方便的是,您无需用手进行比较,并且在注释中写几行很方便。 但是,在测试将数据写入csv文件的对象时,出现了困难。 根据RFC4180, CSV中的行必须用CRLF分隔,并且go fmt用LF替换所有行,这导致以下事实:由于不同的行分隔符,注释中的标准与当前输出不匹配。 我必须为此对象编写常规测试,同时还要通过从其中删除example_重命名该文件。


问题仍然存在,如果使用示例测试和常规测试都对文件(例如query.go进行了测试,是否应该有两个文件example_query_test.goquery_test.go ? 例如,在这里只有一个example_test.go 。 使用搜索“ go test example”仍然很有趣。


我学会了根据Google给出的“编写测试”指南,在Go中编写测试。 我碰到的大多数( 1、2、3、4 )建议将结果与表格的预期设计进行比较


 if v != 1.5 { t.Error("Expected 1.5, got ", v) } 

但是,在比较类型时,熟悉的构造会演变为使用“反射”或类型声明的堆。 或另一个示例,当您需要检查切片或贴图是否具有必要的值时。 代码变得繁琐。 因此,我想为测试编写辅助功能 。 尽管这里的一个好的解决方案是使用库进行测试。 我找到了https://github.com/stretchr/testify 。 它使您可以单行进行比较 。 该解决方案减少了代码量,并简化了测试的读取和支持。


代码分段和测试


为可与多个对象一起使用的高级功能编写测试,可让您立即显着增加测试的代码覆盖率,因为在此测试过程中,将执行单个对象的许多行代码。 如果您将目标设置为仅100%覆盖率,那么在系统的小型组件上编写单元测试的动机就消失了,因为这不会影响代码覆盖率的价值。


另外,如果不检查测试功能的结果,这也不会影响代码覆盖率的值。 您可以获得很高的覆盖率,但无法检测到应用程序中的严重错误。


另一方面,如果您的代码具有许多分支 ,之后又调用了一个庞大的函数,那么将很难用测试将其覆盖。 在这里,您有动力去改进此代码,例如, 所有分支带入一个单独的函数并为其编写单独的测试 。 这将积极影响代码的可读性。


如果代码具有很强的耦合性,那么很可能您将无法对其进行测试,这意味着您将不得不对其进行更改,这将对代码的质量产生积极影响。


结论


在进行此项目之前,我没有设定将测试覆盖100%的目标。 我可以在10个小时的开发过程中得到一个可以运行的应用程序,但是我花了20到30个小时才达到95%的覆盖率。 通过一个小例子,我了解了代码覆盖率的值如何影响其质量以及需要花费多少精力来维护它。


我的结论是,如果您看到仪表板对某人的代码覆盖率很高,那么它几乎没有说明该应用程序的测试水平。 无论如何,您需要自己观察测试。 但是,如果您自己设置了100%诚实的课程,那么这将帮助您更好地编写应用程序。


您可以在以下材料及其注释中阅读有关此内容的更多信息:



扰流板

“涂层”一词大约使用了20次。 不好意思

Source: https://habr.com/ru/post/zh-CN418565/


All Articles