Go 示例测试实现原理剖析
简介
示例测试相对于单元测试和性能测试来说,其实现机制比较简单。它没有复杂的数据结构,也不需要额外的流程控制,其核心工作原理在于收集测试过程中的打印日志,然后与期望字符串做比较,最后得出是否一致的报告。
数据结构
每个测试经过编译后都有一个数据结构来承载,这个数据结构即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 示例测试实现原理剖析的更多相关文章
- 原理剖析-Netty之服务端启动工作原理分析(下)
一.大致介绍 1.由于篇幅过长难以发布,所以本章节接着上一节来的,上一章节为[原理剖析(第 010 篇)Netty之服务端启动工作原理分析(上)]: 2.那么本章节就继续分析Netty的服务端启动,分 ...
- ASP.NET Core 运行原理剖析2:Startup 和 Middleware(中间件)
ASP.NET Core 运行原理剖析2:Startup 和 Middleware(中间件) Startup Class 1.Startup Constructor(构造函数) 2.Configure ...
- iPhone/Mac Objective-C内存管理教程和原理剖析
http://www.cocoachina.com/bbs/read.php?tid-15963.html 版权声明 此文版权归作者Vince Yuan (vince.yuan#gmail.com)所 ...
- ASP.NET Core 运行原理剖析
1. ASP.NET Core 运行原理剖析 1.1. 概述 1.2. 文件配置 1.2.1. Starup文件配置 Configure ConfigureServices 1.2.2. appset ...
- ThreadLocal及InheritableThreadLocal的原理剖析
我们知道,线程的不安全问题,主要是由于多线程并发读取一个变量而引起的,那么有没有一种办法可以让一个变量是线程独有的呢,这样不就可以解决线程安全问题了么.其实JDK已经为我们提供了ThreadLocal ...
- 基本功 | Litho的使用及原理剖析
1. 什么是Litho? Litho是Facebook推出的一套高效构建Android UI的声明式框架,主要目的是提升RecyclerView复杂列表的滑动性能和降低内存占用.下面是Litho官网的 ...
- 0000 - Spring 中常用注解原理剖析
1.概述 Spring 框架核心组件之一是 IOC,IOC 则管理 Bean 的创建和 Bean 之间的依赖注入,对于 Bean 的创建可以通过在 XML 里面使用 <bean/> 标签来 ...
- Visual Leak Detector原理剖析
认识VLD VLD(Visual Leak Detector)是一款用于Visual C++的开源内存泄漏检测工具,我们只需要在被检测内存泄漏的工程代码里#include “vld.h”就可以开启内存 ...
- Spring 中常用注解原理剖析
前言 Spring 框架核心组件之一是 IOC,IOC 则管理 Bean 的创建和 Bean 之间的依赖注入,对于 Bean 的创建可以通过在 XML 里面使用 <bean/> 标签来配置 ...
随机推荐
- python log封装
# _*_ coding:utf-8 _*_ import logging import os import sys import time log_path = os.path.dirname(sy ...
- C++设计模式(转)
在简书看到CharlesW同学学习设计模式的笔记,感觉很有意思(单身狗的妄想),转载下. 转载:https://www.jianshu.com/p/082662126bdd 好的软件设计是多用代码复用 ...
- TPO-19 C1 Discussing A Point Raised In A Lecture
TPO-19 C1 Discussing A Point Raised In A Lecture 第 1 段 1.Listen to a conversation between a student ...
- <数据结构系列1>封装自己的数组——手写动态泛型数组(简化版ArrayList)
哈哈,距离上一次写博客已经快过去半个月了,这这这,好像有点慢啊,话不多说,开始我们的手写动态泛型数组 首先是我们自己写一个自己的动态数组类,代码如下所示: public class Array< ...
- Python中的装饰器的初步理解
什么是装饰器? 装饰器的本质是一个函数,其作用是用来装饰其他的函数,给其他函数附加行的功能. 原则: 1.不能修改被装饰函数的源码. 2.不能改变被装饰函数的调用方式. 那么什么是函数? 简单的来说: ...
- nginx之location(root/alias)
location配置 1. 语法规则(按优先级) = 表示精确匹配,优先级最高 ^~ 表示uri以某个常规字符串开头,用于匹配url路径(而且不对url做编码处理,例如请求/s ...
- Vue 列表渲染及条件渲染实战
条件渲染 有时候我们要根据数据的情况,决定标签是否进行显示或者有其他动作.最常见的就是,表格渲染的时候,如果表格没有数据,就显示无数据.如果有数据就显示表格数据. Vue 帮我们提供了一个v-if的指 ...
- Myeclipse2014 用Maven创建第一个web项目(1)---helloworld
一.创建项目 1.Eclipse中用Maven创建项目 2.继续Next 3.选maven-archetype-webapp后,next 4.填写相应的信息,Packaged是默认创建一个包,不写也可 ...
- Android笔记-1
1.点击按钮出现小窗口(响应事件) 配置方式: Activity_main.xml文件中:<Button (输入)android: onClick=”test1” /> MainActiv ...
- eg_5
问题描述:输入两个字符串,从第一字符串中删除第二个字符串中所有的字符. 例如,输入”They are students.”和”aeiou”,则删除之后的第一个字符串变成”Thy r stdnts.” ...