前置说明

本文以及接下来的文章都是来自 https://quii.gitbook.io/learn-go-with-tests/ 这个系列文章的。

主要分析说明的部分是 Build An Application 部分。

这并不是原文的翻译,而是记录一些自己的东西。混合式笔记,实现书中代码,加上我的思考

正文开始

看到标题,问题就来了,前面的文件结构都是按照我以前的认知来构建的,不过 golang 的结构,和跟以往了解的 php 有一些区别,虽然看了一些 golang 的结构,但是感觉使用起来不是很顺手。正好借着本篇文章在了解一下吧。

另外关于 TDD 的问题,我个人觉得随着经验的增长可以把每一步前进的快一点,比如最开始构造空方法,可以前进到,直接构建默认返回值的步骤。

今天的需求就是利用命令行实现一些请求的方法,而不仅仅是用 http。

哦吼,文章的第一段就是把 main 移动到 cmd/webserver 文件夹下,与我们的很相似,只要改个名就好了,展示一下我们当下的结构。另外包名文章中用的 poker,与我们的不一致,那我们就保持好我们当下的就好了。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cgURs7HJ-1633331410384)(https://www.cimple.ink/static/images/20211003101701.png)]

现在开始在 cmd 下新建一个 cli 的文件夹,并且创建 main.go 文件。让其成为命令行的入口。

下面开始解决输入用户名记录获胜。从测试开始。新建 cli_test 注意,这个文件在根目录一下,而不是在 cmd 目录下。

func TestCli(t *testing.T) {playerStore := &StubPlayerScore{}cli := &CLI{playerStore}cli.PlayPoker()if len(playerStore.winCalls) != 1 {t.Fatalf("expected a win call but didn't get any")}
}

可以看到,与我们前面的测试是很相似的,不过就是初始化不同了,这边初始化的是 CLI,以前的是 Server,这里是 PlayPoker,以前是 ServerHTTP。而且,参数也是一致的,都是 store。目前是会报错的,因为没有 CLI,准备实现它。

// CLI.go
type CLI struct {playerStore PlayerStore
}func (c *CLI) PlayPoker()  {}

注意这个文件是在根目录下创建的。目前测试不会报错,但是测试会不通过,因为没有任何返回。

func (c *CLI) PlayPoker() {c.playerStore.RecordWin("Cleo")
}

优化代码,现在测试可以通过了,但是无法通过输入的方式,传入用户名。所以接下来就要处理用户输入了。

type CLI struct {playerStore PlayerStorein io.Reader
}
func TestCli(t *testing.T) {in := strings.NewReader("Chris wins\n")playerStore := &StubPlayerScore{}cli := &CLI{playerStore, in}cli.PlayPoker()if len(playerStore.winCalls) != 1 {t.Fatalf("expected a win call but didn't get any")}got := playerStore.winCalls[0]want := "Chirs"if got != want {t.Errorf("didn't record correct winner, got %q, want %q", got, want)}
}

增加了一个 reader 来处理用户输入。

type CLI struct {playerStore PlayerStorein io.Reader
}func (c *CLI) PlayPoker() {reader := bufio.NewScanner(c.in)reader.Scan()c.playerStore.RecordWin(extractWinner(reader.Text()))
}func extractWinner(userInput string) string {return strings.Replace(userInput, " wins", "", 1)
}

这是优化后的代码,增加了一个 reader 来处理用户输入。为什么用 io.Reader 呢,这是个通用的接口,os.Stdin 也实现了这个接口。测试的时候用 strings.NewReader 来生成 reader 来测试。最后的 extractWinner 就是过滤一些文本,现在执行测试,可以通过了。另外 bufio,这个需要看下文档了。因为这个 io 实现了一些后续需要用到的东西。Scan 方法读取一行,Text 方法返回读取到的文本。

func main() {fmt.Println("Let's play poker")fmt.Println("Type {Name} wins to record a win")db, err := os.OpenFile(dbFileName, os.O_RDWR | os.O_CREATE, 0666)if err != nil {log.Fatalf("problem opening %s %v", dbFileName, err)}store, err := go_http_application_with_tdd.NewFileSystemPlayerStore(db)if err != nil {log.Fatalf("problem creating file system player store, %v", err)}game := go_http_application_with_tdd.CLI{store, os.Stdin}game.PlayPoker()
}

现在处理运行部分,不过目前会报错,因为 store 和 in 都是私有的,要解决掉,后续文章讲述了如何更早的发现这个问题。

将测试文件的包名改为 包名_test 。这样就只能可以访问导出的公开的东西了。修改完包名后,就很多报错了。因为很多测试的数据都是在 _test 文件中定义的。创建一个 testing 文件用来保存我们的测试数据。把方法都独立出来。

func TestCli(t *testing.T) {t.Run("record chris win from user input", func(t *testing.T) {in := strings.NewReader("Chris wins\n")playerStore := &go_http_application_with_tdd.StubPlayerScore{}cli := &go_http_application_with_tdd.CLI{playerStore, in}cli.PlayPoker()go_http_application_with_tdd.AssertPlayerWin(t, playerStore, "Chris")})t.Run("record cleo win from user input", func(t *testing.T) {in := strings.NewReader("Cleo wins\n")playerStore := &go_http_application_with_tdd.StubPlayerScore{}cli := &go_http_application_with_tdd.CLI{playerStore, in}cli.PlayPoker()go_http_application_with_tdd.AssertPlayerWin(t, playerStore, "Cleo")})
}

修改测试代码。

type CLI struct {playerStore PlayerStorein          *bufio.Scanner
}func NewCLI(store PlayerStore, in io.Reader) *CLI {return &CLI{playerStore: store,in: bufio.NewScanner(in),}
}func (c *CLI) PlayPoker() {userInput := c.readline()c.playerStore.RecordWin(extractWinner(userInput))
}func extractWinner(userInput string) string {return strings.Replace(userInput, " wins", "", 1)
}func (c *CLI) readline() string {c.in.Scan()return c.in.Text()
}

通过本篇文章,我们了解了 New 方法的作用,这个方法,可以是我们的属性不用暴露出去,保持封装。

现在应该都 ok 了,不过两个 main 里面。是有重复的,那么就把这个重复的独立出来。

func FileSystemPlayerStoreFromFile(path string) (*FileSystemPlayerStore, func(), error) {db, err := os.OpenFile(path, os.O_RDWR | os.O_CREATE, 0666)if err != nil {return nil, nil, fmt.Errorf("Problem opening %s %v", path, err)}closeFunc := func() {db.Close()}store, err := NewFileSystemPlayerStore(db)if err != nil {return nil, nil, fmt.Errorf("problem creating file system player store, %v", err)}return store, closeFunc, nil
}

然后修改两个 main 的调用就 ok 了。

总结

今天了解了 cli,以及如何更好的测试,还有关于包结构的问题。这篇文章让我有了很多的思考。但是还没法写出来。先把要总结的问题或者思考列出来吧,以后再总结

  • 合理的总结包结构
  • 测试的时候创建 testing 文件存放所有的测试方法,不污染其他方法。
  • 测试包的包名 要 包名_test 这样可以测试公开的属性,不会让未暴露的属性被使用,模拟真正的调用
  • New 方法就是方式 struct 的属性被暴露出来,保持合适的封装性。

使用tdd构建golang Web 应用(4)相关推荐

  1. Golang Web入门(4):如何设计API

    Golang Web入门(4):如何设计API 摘要 在之前的几篇文章中,我们从如何实现最简单的HTTP服务器,到如何对路由进行改进,到如何增加中间件.总的来讲,我们已经把Web服务器相关的内容大概梳 ...

  2. Golang Web入门(2):如何实现一个RESTful风格的路由

    Golang Web入门(2):如何实现一个RESTful风格的路由 摘要 在上一篇文章中,我们聊了聊在Golang中怎么实现一个Http服务器.但是在最后我们可以发现,固然DefaultServeM ...

  3. golang web 服务器 request 与 response 处理

    golang web 服务器 request 与 response 处理 介绍常见 web 服务的实现与输入.输出数据处理.包括:静态文件服务.js 请求支持.模板输出.表单处理.Filter 中间件 ...

  4. [golang Web开发] 1.golang web开发:简介以及web服务器的搭建以及http协议简介

    一.web开发简介 1.简介 Web应用在我们的生活中无处不在,看看我们日常使用的各个应用程序,它们要么是 Web应用,要么是移动 App 这类 web应用的变种,无论哪一种编程语言,只要它能够开发出 ...

  5. 在k8s中使用gradle构建java web项目镜像Dockerfile

    在k8s中使用gradle构建java web项目镜像Dockerfile FROM gradle:6-jdk8 AS build COPY --chown=gradle:gradle . /home ...

  6. CentOS Docker安装配置部署Golang web helloworld

    目录[阅读时间:约5分钟] 一.Docker简介 二.Docker的安装与配置[CentOS环境] 三.Docker部署Golang web helloworld 四.Docker与虚拟机的区别 五. ...

  7. flask url构建_如何为生产构建构建Flask-RESTPlus Web服务

    flask url构建 by Greg Obinna 由格雷格·奥比纳(Greg Obinna) 如何为生产构建构建Flask-RESTPlus Web服务 (How to structure a F ...

  8. 使用 PHP 构建的 Web 应用如何避免 XSS 攻击

    本文首先简单介绍开发测试人员如何对 Web 应用进行 XSS 漏洞测试,如何借助工具绕过客户端 JavaScript 校验输入恶意数据:然后针对使用 PHP 语言构建的 Web 站点,从在输出端对动态 ...

  9. Linux -- 利用IPS(***防御系统) 构建企业Web安全防护网

    一.IPS系统简介 (应用层上应用) 防火墙只在网络层上应用,IPS 和防火墙相比,检测及过滤功能更为强大,它通过串联在网络主干线路上,对防火 墙所不能过滤的***进行过滤.这样一个两级的过滤模式,可 ...

最新文章

  1. 强大的DataGrid组件[13]_字段过滤(Filter)——Silverlight学习笔记[21]
  2. iis php性能优化,IIS 6 的 PHP 最佳配置方法
  3. 软件设计原则(五)接口隔离原则 -Interface Segregation Principle
  4. IOS开发基础知识--碎片8
  5. 【论文笔记】One Millisecond Face Alignment with an Ensemble of Regression Trees
  6. 机器学习学习吴恩达逻辑回归_机器学习基础:逻辑回归
  7. review_core_basic_java(1)java程序设计概述
  8. 使用Google GSON:额外的赠品:第二部分
  9. python 开发api_使用FastAPI和Python快速开发高性能API
  10. put请求方式参数如何传_TP5请求(request)变量
  11. pc计算机怎么设置域名管理,如何设置域名的DNS服务器 -电脑资料
  12. 深度学习(tensorflow+keras)性能问题
  13. 转自JavaEye --Oracle函数大全
  14. Qt 之 QQ系统表情(二)
  15. 第五次打卡 模型融合
  16. 股权-公司治理的至高点,如何合理运用股权(融人、融资、融市场) 张明若
  17. JAVA学习日志 关于周易数字卦的一个算法
  18. 阿愚呱呱作为一个非技术人员,是如何做到不到3年时间成为RPA行业的一个头部IP的?
  19. cad服务器手动改自动,手动挡改为自动挡,只需加装这“神器”老司机3000元就能...
  20. 图像特效---哈哈镜效果滤镜

热门文章

  1. 【封面】把握软件产业发展新机遇 ——记第二十届中国国际软件博览会
  2. Centos8安装vsftpd
  3. Android的Elevation与TranslationZ探索
  4. 改进的IAYOLO---GDIP:Gated Differentiable Image Processing for Object-Detection inAdverse Conditions
  5. 有坐标的主买量占比主卖占比判断操盘通达信指标公式源码 附效果图
  6. (附源码)php校园二手交易网站 附源码041148
  7. 米什金版货币金融学简答及一些知识点
  8. 本科以下别学 IT 了,切身经历
  9. 数据销毁,对于数据中心运营意味着什么?
  10. A Survey on Transfer Learning 总结