简介
  
  示例测试相对于单元测试和性能测试来说,其实现机制比较简单。它没有复杂的数据结构,也不需要额外的流程控制,其核心工作原理在于收集测试过程中的打印日志,然后与期望字符串做比较,最后得出是否一致的报告。
  
  数据结构
  
  每个测试经过编译后都有一个数据结构来承载,这个数据结构即InternalExample:
  
  type InternalExample struct {
  
  Name string // 测试名称
  
  F func() // 测试函数
  
  Output string // 期望字符串
  
  Unordered bool // 输出是否是无序的
  
  }
  
  比如,示例测试如下:
  
  // 检测乱序输出
  
  func ExamplePrintNames() {
  
  gotest.PrintNames()
  
  // Unordered output:
  
  // Jim
  
  // Bob
  
  // Tom
  
  // Sue
  
  }
  
  该示例测试经过编译后,产生的数据结构成员如下:
  
  InternalExample.Name = "ExamplePrintNames";
  
  InternalExample.F = ExamplePrintNames()
  
  InternalExample.Output = "Jim\n Bob\n Tom\n Sue\n"
  
  InternalExample.Unordered = true;
  
  其中Output是包含换行符的字符串。
  
  捕获标准输出
  
  在示例测试开始前,需要先把标准输出捕获,以便获取测试执行过程中的打印日志。
  
  捕获标准输出方法是新建一个管道,将标准输出重定向到管道的入口(写口),这样所有打印到屏幕的日志都会输入到管道中,如下图所示:
  
  测试开始前捕获,测试结束恢复标准输出,这样测试过程中的日志就可以从管理中读取了。
  
  测试结果比较
  
  测试执行过程的输出内容最终也会保存到一个string类型变量里,该变量会与InternalExample.Output进行比较,二者一致即代表测试通过,否则测试失败。
  
  输出有序的情况下,比较很简单只是比较两个String内容是否一致即可。无序的情况下则需要把两个String变量排序后再进行对比。
  
  比如,期望字符串为:"Jim\n Bob\n Tom\n Sue\n",排序后则变为:"Bob\n Jim\n Sue\n Tom\n"
  
  测试执行
  
  一个完整的测试,过程将分为如下步骤:
  
  捕获标准输出
  
  执行测试
  
  恢复标准输出
  
  比较结果
  
  下面,由于源码非常简单,下面直接给出源码:
  
  func runExample(eg InternalExample) (ok bool) {
  
  if *chatty {
  
  fmt.Printf("=== RUN %s\n", eg.Name)
  
  }
  
  // Capture stdout.
  
  stdout := os.Stdout // 备份标输出文件
  
  r, w, err := os.Pipe() // 创建一个管道
  
  if err != nil {
  
  fmt.Fprintln(os.Stderr, err)
  
  os.Exit(1)
  
  }
  
  os.Stdout = w // 标准输出文件暂时修改为管道的入口,即所有的标准输出实际上都会进入管道
  
  outC := make(chan string)
  
  go func() {
  
  var buf strings.Builder
  
  _, err := io.Copy(&buf, r) // 从管道中读出数据
  
  r.Close()
  
  if err != nil {
  
  fmt.Fprintf(os.Stderr, "testing: copying pipe: %v\n", err)
  
  os.Exit(1)
  
  }
  
  outC <- buf.String() // 管道中读出的数据写入channel中
  
  }()
  
  start := time.Now()
  
  ok = true
  
  // Clean up in a deferred call so we can recover if the example panics.
  
  defer func() {
  
  dstr := fmtDuration(time.Since(start)) // 计时结束,记录测试用时
  
  // Close pipe, restore stdout, get output.
  
  w.Close() // 关闭管道
  
  os.Stdout = stdout // 恢复原标准输出
  
  out := <-outC // 从channel中取出数据
  
  var fail string
  
  err := recover()
  
  got := strings.TrimSpace(www.yongshiyule178.com) // 实际得到的打印字符串
  
  want := strings.TrimSpace(eg.Output) // 期望的字符串
  
  if eg.Unordered { // 如果输出是无序的,则把输出字符串和期望字符串排序后比较
  
  if sortLines(got) != sortLines(want) && err == nil {
  
  fail = fmt.Sprintf("got:\n%s\nwant (unordered):\n%s\n", out, eg.Output)
  
  }
  
  } else { // 如果输出是有序的,则直接比较输出字符串和期望字符串
  
  if got != want && err == nil {
  
  fail = fmt.Sprintf("got:\n%s\nwant:\n%s\n", got, want)
  
public class PeopleA www.tiaotiaoylzc.com/ implements People {
@Override
public void update(News news) {
System.out.println("这个新闻真好看");
}
}
public class PeopleB implements People {
@Override
public void update(News www.feifanyule.cn/ news) {
System.out.println("这个新闻真无语");
}
}
public class PeopleC implements People {
@Override
public void update(News news) {
System.out.println("这个新闻真逗");
}
}
客户端:

public class Main {
public static void main(String[dasheng178.com] args) {
Subject subject = new NewsSubject();
subject.add(new PeopleA(www.yongshiyule178.com));
subject.add(new PeopleB());
subject.add(new PeopleC());
subject.update(www.fengshen157.com/);
}
  if fail != "" || err != nil {
  
  fmt.Printf("--- FAIL: %s (%s)\n%s", eg.Name, dstr, fail)
  
  ok = false
  
  } else if *chatty {
  
  fmt.Printf("-www.mytxyl1.com-- PASS: %s (%s)\n", eg.Name, dstr)
  
  }
  
  if err != nil {
  
  panic(err)
  
  }
  
  }()
  
  // Run example.
  
  eg.F()
  
  return
  
  }
  
  示例测试执行时,捕获标准输出后,马上启动一个协程阻塞在管道处读取数据,一直阻塞到管道关闭,管道关闭也即读取结束,然后把日志通过channel发送到主协程中。
  
  主协程直接执行示例测试,而在defer中去执行关闭管道、接收日志、判断结果等操作。

Go 示例测试实现原理剖析的更多相关文章

  1. 原理剖析-Netty之服务端启动工作原理分析(下)

    一.大致介绍 1.由于篇幅过长难以发布,所以本章节接着上一节来的,上一章节为[原理剖析(第 010 篇)Netty之服务端启动工作原理分析(上)]: 2.那么本章节就继续分析Netty的服务端启动,分 ...

  2. ASP.NET Core 运行原理剖析2:Startup 和 Middleware(中间件)

    ASP.NET Core 运行原理剖析2:Startup 和 Middleware(中间件) Startup Class 1.Startup Constructor(构造函数) 2.Configure ...

  3. iPhone/Mac Objective-C内存管理教程和原理剖析

    http://www.cocoachina.com/bbs/read.php?tid-15963.html 版权声明 此文版权归作者Vince Yuan (vince.yuan#gmail.com)所 ...

  4. ASP.NET Core 运行原理剖析

    1. ASP.NET Core 运行原理剖析 1.1. 概述 1.2. 文件配置 1.2.1. Starup文件配置 Configure ConfigureServices 1.2.2. appset ...

  5. ThreadLocal及InheritableThreadLocal的原理剖析

    我们知道,线程的不安全问题,主要是由于多线程并发读取一个变量而引起的,那么有没有一种办法可以让一个变量是线程独有的呢,这样不就可以解决线程安全问题了么.其实JDK已经为我们提供了ThreadLocal ...

  6. 基本功 | Litho的使用及原理剖析

    1. 什么是Litho? Litho是Facebook推出的一套高效构建Android UI的声明式框架,主要目的是提升RecyclerView复杂列表的滑动性能和降低内存占用.下面是Litho官网的 ...

  7. 0000 - Spring 中常用注解原理剖析

    1.概述 Spring 框架核心组件之一是 IOC,IOC 则管理 Bean 的创建和 Bean 之间的依赖注入,对于 Bean 的创建可以通过在 XML 里面使用 <bean/> 标签来 ...

  8. Visual Leak Detector原理剖析

    认识VLD VLD(Visual Leak Detector)是一款用于Visual C++的开源内存泄漏检测工具,我们只需要在被检测内存泄漏的工程代码里#include “vld.h”就可以开启内存 ...

  9. Spring 中常用注解原理剖析

    前言 Spring 框架核心组件之一是 IOC,IOC 则管理 Bean 的创建和 Bean 之间的依赖注入,对于 Bean 的创建可以通过在 XML 里面使用 <bean/> 标签来配置 ...

随机推荐

  1. Python字符串符号:双引号/单引号用法注解。

    众所周知python中单引号和双引号常常被我们所使用,例如print.input等等. 但是对于打印输出所引导的字符串大多都是用双引号的形式来做,"Hello,python!",而 ...

  2. 网格系统-bootStrap4常用CSS笔记

    .row 定义一行 .col 均分列数,最多一行12列.每列左右间隙各15px .col-{1到12} 定义在所有屏幕下的列宽 .col-{sm|md|lg|xl}-{1到12} 定义在指定屏幕下该列 ...

  3. 脚本处理iOS的Crash日志

    背景 当我们打包app时,可以选择生成对应的符号表,其保存 16 进制函数地址映射信息,通过给定的函数起始地址和偏移量,可以对应函数具体信息以供分析. 所以我们拿到测试给的闪退日志(.crash)时, ...

  4. 6.把建模工具导出的dea文件导入到three.js程序中

    1.使用Three.js渲染导出的DAE 在Three.js中使用Collada(即.dae)文件的话,首先得要用到 ColladaLoader.js. 但是这个ColladaLoader.js并不包 ...

  5. 从零系列--node爬虫利用进程池写数据

    1.主进程 const http = require('http'); const fs = require('fs'); const cheerio = require('cheerio'); co ...

  6. 关于nodejs中遇到mysql默认8小时连接断开机制的终极简单解决方案

    由于mysql默认8小时连接无访问,就会断开.为此查了一下资料,有同种比较简单的解决方案: 1. 增加 MySQL 的 wait_timeout 属性的值. 修改 /etc/mysql/my.cnf文 ...

  7. 满帮集团CEO:未来将向“智慧型”公司转变,要成为一家生态公司

    谁都想成为下一个滴滴.显然,王刚也希望在物流业,货车帮与运满满在合并后,能够企及滴滴的高度. 货车帮与运满满,都曾是货运物流领域的翘楚,也因为业务的竞争关系有过水火不容厮杀.但最终还是在资本与地方政府 ...

  8. ipmitool命令详解

    基础命令学习目录首页 原文链接:https://www.cnblogs.com/EricDing/p/8995263.html [root@localhost ~]# yum install -y i ...

  9. cal命令详解

    基础命令学习目录首页 原文链接:https://www.yiibai.com/linux/cal.html cal命令可以用来显示公历(阳历)日历.公历是现在国际通用的历法,又称格列历,通称阳历.“阳 ...

  10. vue ,v-for循环对象,不是深度克隆? 数据改变了但是页面元素没有更新。问题解决

    <div id="app"> <ul > <li v-for="(val,key,idx) in list" > {{key ...