Astrisk Blog

单元测试学习之路总结

• UNITTEST

之前为了在团队推单元测试,让研发能够更好地学习单元测试,总结下了下自己前几年学习单元测试的过程,并且挑选了一些自己看过的经典书籍或者一些相关的视频资料。

《.NET单元测试艺术》

这是本纯粹讲单元测试的书,也是唯一一本用C#来实现的单元测试书籍。作者重新定义了单元测试,更准确的说是优秀单元测试特性和价值。 书不但介绍如何从零开始用Nunit编写单元测试;也讲解如何解除代码依赖来实现单元测试,包含了STUB和MOCK;同时还特别用一章(第七章)讲解了优秀单元测试的支柱;附录A,设计和可测试性,也是值得细读和品位。同时书中还介绍了C#世界的优秀MOCK框架Rhino Mocks(优点是功能强大,缺点是语法比较麻烦,要是有段时间没用,可能需要重新温习);而且书中还讲解了管理和组织单元测试的技术以及如何确保项目中单元测试具有高质量。 总之,这是一本测试开发,白盒测试人员和开发必读的单元测试书籍。读完后使你对单元测试的价值有新的认识,并知道如何写出优秀的单元测试。

OO设计原则和《Head Frist 设计模式》

完成了第一步后,我们知道如何编写优秀单元测试和很多遗留代码为什么很难添加单元测试。但是我们新设计的程序或新编写的代码怎么才能具有测试性。既然我们使用的是面向对象的语言,那就不得不先了解OO设计原则和面向对象设计模式。关于这块,我也只是了解,只能给出相关的学习资料,不能给出太多意见。我是先去了解OO设计原则,再去看设计模式,我选的是《Head Frist 设计模式》,主要是这本书写的比较风趣,读起来比较轻松,易懂,适合入门者。 参考链接: http://www.codeproject.com/Articles/567768/Object-Oriented-Design-Principles http://blog.csdn.net/Eric_Jo/article/details/6818552 这个可以去网络搜索下,大把把的资料。

《面向对象分析和设计(第3版)》

经过第二步,我发现自己在写代码时,还是按照之前的顺序结构去实现,很难一开始就用面向对象的思想去思考问题,分解问题,抽象问题。这时候买了这本书,去学习怎么用面向对象的思想去分解问题,抽象和结构化程序设计,顺便把UML给了解了一下。虽然看的糊里糊涂的,但是还是有些收获。(尽管自己心里觉得是看明白的) MOCK中国,正好有这些视频教程,如果有时间,可以把书和视频结合学习,应该会轻松些。不过课程为12周,需要些毅力坚持哦,小伙伴们。课程链接:http://www.mooc.cn/course/250.html

接口编程

经过以上的学习,我们知道了代码应该依赖于接口和抽象,但是怎么样定义接口和使用接口编程,你是不是还有疑惑或者再寻找相关的例子,当然对程序猿来说,最好还要有code。那这个课程一定可以给你一些启发。本课程从C#的interface,Abstract Classes 和Concrete Classes的基础概念开始,一步步讲解用面对接口方式构建可扩展、可维护和可测试的应用程序。而且课程里面还包含了接口隔离原则、接口和抽象类对比、更新现有接口、依赖注入、MOCK等高级主题。特别是课程里面的Repository Pattern值得经常编写跟数据层交互代码的童鞋参考。 课程链接url:https://app.pluralsight.com/library/courses/csharp-interfaces/table-of-contents

《测试驱动的面向对象软件开发》

这是本TTD实践的指南书籍。虽然书里面的例子,是用Java来实现的,但是书中传播的思想,则不分语言,同样可以使用在C#编程中。抛开TTD的编程实践,书中的第六章(面向对象风格)、第七章(实现面向对象设计)以及第八章(基于第三方代码构建)能够给我们在程序设计提供一些思考,构建有层次感的代码,使应用的结构向“端口和适配器”架构发展。第四部分“可持续的测试驱动开发”用五章来讲解测试,如果实现测试的可读性、灵活性、表现力以及测试诊断。本书我也只是看过第一遍,里面很多精神没有领会,不过这个确实是本好书。同时关于TTD部分,零迭代创建行走骨架,一开始构建可交付程序实现真正的敏捷也是很有意思。

《整洁代码》和《重构 改善既有代码的设计》

这两本书就不需要更多的废话了,前者我看完了,后者看了一部分。里面提到的很多思想跟我们构建可测试性程序是一致的。不过书中的例子都是Java代码。

SHELL-CHATTR

• LINUXSHELL

功能 chattr命令用来修改文件属性,命令的使用跟chmod命令相似。

chattr命令有一个特别的选项i。chattr +i filename 将使得这个文件被标识为永远不变。这个文件不能被修改,连接,删除,即使root用户也不行。这个文件属性只能被root设置和删除。类似的a选项将会吧文件标记为只能追加数据。

不是所有的文件系统都支持这个命令,在CentOS 7-ext4中试验支持。

\# chattr +i file1.txt

# 使用chattr命令设置的属性,不会在ls -la中显示,用lsattr 显示相关属性

\# lsattr file1.txt

UNITTEST-Introduction

• UNITTEST

单元测试定义

相关概念

Fakes 、Mocks、Stubs

AAA 模式 和Record—Replay模式

编写单元测试步骤

SONARQUBE-CONCEPTS

• example

SonarQube 简介

Sonar 是一个用于代码质量管理的开源平台,用于管理源代码的质量。Sonar 通过插件形式,可以支持包括Java/C#/C/C++/PL/SQL/Cobol/JavaScrip/Groovy 等等二十几种编程语言的代码质量管理与检测。

Sonar 可以从以下七个维度检测代码质量,而作为开发人员至少需要处理前5种代码质量问题:

  1. 不遵循代码标准
    Sonar 可以通过PMD,CheckStyle,Findbugs 等等代码规则检测工具规范代码编写。
  2. 潜在的缺陷
    Sonar 可以通过PMD,CheckStyle,Findbugs 等等代码规则检测工具检测出潜在的缺陷。
  3. 糟糕的复杂度分布
    文件、类、方法等,如果复杂度过高将难以改变,这会使得开发人员难以理解它们,且如果没有自动化的单元测试,对于程序中的任何组件的改变都将可能导致需要全面的回归测试。
  4. 重复
    显然程序中包含大量复制粘贴的代码是质量低下的,Sonar 可以展示源码中重复严重的地方。
  5. 注释不足或者过多
    没有注释将使代码可读性变差,特别是当不可避免地出现人员变动时,程序的可读性将大幅下降。而过多的注释又会使得开发人员将精力过多地花费在阅读注释上,亦违背初衷。
  6. 缺乏单元测试
    Sonar 可以很方便地统计并展示单元测试覆盖率。
  7. 糟糕的设计
    通过 Sonar 可以找出循环,展示包与包、类与类之间的相互依赖关系,可以检测自定义的架构规则。通过 Sonar 可以管理第三方的 jar 包,可以利用 LCOM4 检测单个任务规则的应用情况,检测耦合。

SONARQUBE CONCEPTS

Duplication

Documentation

Complexity

Complexity 圈复杂度,也被称为McCabe度量。它简单归结为一个方法中’if’,‘for’,’while’等块的数目。每个方法默认有最小的值为1,按照控制流一直往下通过程序,一旦遇到以下关键字或者其同类的词,就加1:if,while,repeat,for,and,or,给case(switch)语句中的每一种情况加1.《代码大全》的作者里对圈复杂度的建议:

  1. 0~5:子程序可能还不错。
  2. 6~20:得想办法简化子程序。
  3. 10+:把子程序得某一部分拆成另一个子程序并调用它。
    • Complexity 默认为各个文件复杂度的加和
    • Complexity /function 方法复杂度
    • Complexity /file 文件复杂度
    • Complexity /class 类复杂度

Coverage

Size

Bug

An issue that represents something wrong in the code. If this has not broken yet, it will, and probably at the worst possible moment. This needs to be fixed. Yesterday.

Vulnerability

A security-related issue which represents a potential backdoor for attackers. See also Security-related rules

Code Smell

A maintainability-related issue in the code. Leaving it as-is means that at best maintainers will have a harder time than they should making changes to the code. At worst, they’ll be so confused by the state of the code that they’ll introduce additional errors as they make changes.

Issue

When a component does not comply with a coding rule, an issue is logged (was violation prior to SonarQube 3.6) on the snapshot.

An issue can be logged on a source file or a unit test file. There are 3 types of issue:

SHELL-SS

• LINUXSHELL

ss是socket statistics的简写,用来dump socket统计信息。可以显示网络连接信息,功能跟netstat类似,但是据说性能上要比netstat要好几倍,所以在高负载的服务器上查看网络连接状态,最好使用ss,而不是netstat。

语法

$ ss [options] [ FILTER ]

UNITTEST-testcase Design

• UNITTEST

当我们在做单元测试的时候,很多同事都会提到这样一个问题:如何设计单元测试用例,应该从哪些角度或者维度来设计测试用例?

用测试的思维来看待单元测试,就是我们首先要理解需求,清楚需求,然后才能知道怎么去设计用例,写测试用例。其实对于单元测试,也是一样的。只不过需求的表现或者表达方式不一样而已。

那什么是单元测试的需求呢?以及从哪些方面来进行单元测试用例设计?这就是本篇post的主题。

理解单元测试需求

单元测试的被测试对象是类的公共接口(我们对private方法不做要求)。如果需要比较完整地理解一个类的职责,最好不要孤立地去考虑被测试类/方法,而应该从模块等比较高的层面去理解类的行为,用角色、职责和协作者的关系来思考被测试对象,理解了被测对象扮演的角色,有什么样的职责,以及需要的协作对象。具体到被测试的方法,需要清楚:1. 被测试方法的使用场景;2.方法的行为以及上下文(输入,输出);3. 输入输出的限制条件;4. 异常处理;5. 返回值类型等。

那这些需求从哪里获取呢?主要有几个来源:1. 概要设计 2. 详细设计 3. 类接口文档 4. 源代码

我在做单元测试的时候,会去看源码,但是比较少用白盒测试方法去做用例设计,更多地是使用黑盒用例设计方法。我们把对外接口用契约方式来相互协作,开发者只要满足契约即可,内部的实现可以使用不用算法。这样设计的单元测试用例健壮性比较好,只要接口契约不改,用例一般不会有问题。

单元测试用例设计

在理解了测试需求后,我们就可以来进行单元测试用例设计。一般我们要求开发者大致需要考虑以下几个方面的测试用例。

当然并不是说每个被测试方法的单元测试用例都要覆盖以上五个方面。这里更多地是起到指导作用,同时在做UT review时,我们也要从这个方面来考虑用例是否完整。而且一个方面也不一定是一条case,有可能需要多条case。

质量可视化之TEAMCITY数据

• TEAMCITY

背景

之前为了在公司推质量可视化项目,把公司持续集成平台,sonarqube平台的质量数据做收集并集中展示,以此来使得项目过程质量更加透明和可度量,以此来推动研发团队重视质量。

公司的持续集成平台用的是teamcity,如何把持续集成的构建相关质量数据收集起来,是本篇blog的目标。同时在调研过程中也遇到了些问题,并根据调研情况,我们也调整了相应的方案。

TEAMCITY REST API

官方文档

URL Structure

The general structure of the URL in the TeamCity API is teamcityserver:port//app/rest/, where

备注:其中buildType对应Build configuration

Locator

In a number of places, you can specify a filter string which defines what entities to filter/affect in the request. This string representation is referred to as “locator” in the scope of REST API. The locators formats can be:

Examples

意外

从以上资料来看,teamcity的api还是比较简单,好用。但是我在实际获取数据的时候发现数据不准确,从api获取到的数据经常要比teamcity UI和数据库里的数据要少。

最后我放弃了teamcity api获取数据,改用从teamcity的数据库里直接读取相应数据。

TeamCity 数据库查询SQL

select count(*) from history 
join build_type_mapping on build_type_mapping.int_id = history.build_type_id
where ext_id = 'NewMedia_TVDissector_TrackerJS_BaseIntegrationJstracker'
select count(*) from history 
join build_type_mapping on build_type_mapping.int_id = history.build_type_id
where ext_id = 'NewMedia_TVDissector_TrackerJS_BaseIntegrationJstracker'
and status != 1
select 
    build_data_storage.build_id, 
    build_type_mapping.ext_id as 'build_type_id',
    data_storage_dict.value_type_key as 'metric_name', 
    build_data_storage.metric_value,
    history.build_number,
    history.build_finish_time_server
from 
    build_data_storage
join 
    data_storage_dict on build_data_storage.metric_id = data_storage_dict.metric_id
join
    history on build_data_storage.build_id = history.build_id
join
    build_type_mapping on history.build_type_id = build_type_mapping.int_id
where build_type_id='bt354';

备注:build configuration id 即上面sql中的ext_id

质量可视化之SONARQUBE-RESTAPI

• SONARQUBE

背景

之前为了在公司推质量可视化项目,把公司持续集成平台,sonarqube平台的质量数据做收集并集中展示,以此来使得项目过程质量更加透明和可度量,以此来推动研发团队重视质量。

在这个过程中,涉及到几个系统的相关质量数据获取,其中SONARQUBE上的数据就是很大一块。但是由于SONAR相关的资料看起来有些晦涩,搞了段时间才弄明白。关于webapi,gitlab的相关文档就比sonar要好很多,而且每个api都有对应的示例,简洁明了。

接口集成测试框架

• INTEGRATIONTEST

背景

公司大数据平台通用接口需要一个测试工具,既能方便地调用代码,实现类似UnitTest测试,又能调用RESTAPI。没有找到合适地工具,就自己动手基于Nunit写了这个框架。 框架采用数据驱动方式,把测试代码和测试数据分离,根据测试用例文件会动态生成多个case,并执行。

实现思路

测试数据

框架引用

框架用到以下dll,请确保加载以下dll:

测试代码组成

测试代码由两部分组成:用例工厂和测试代码

编写测试

如果测试项目需要连接数据库,则需要为测试项目增加app.config文件,并在app.config文件中定义数据库连接字符串

执行测试

安装完dotCover,在测试类和每个测试方法左边会有个圆圈图标,点击圆圈,执行测试、debug或进行覆盖率分析

完整例子

integrationTest2

[TestFixture]
public class DatasController_installTest
{
     [SetUp]
      public void Indata()
      {
          DateTime dt = DateTime.ParseExact("20150407", "yyyyMMdd",
              System.Globalization.CultureInfo.CurrentCulture);
          DatasController controller = new DatasController();
          controller.PostUpdateTimes(dt);
      }
    [Test, TestCaseSource(typeof (TestCaseFactoryMethod), "installTest")]
    public void WebApiInstallCount(JObject input, List<InstallModel> expValue)
    {
        string message;
        //
        var pids = input["pids"].ToString();
        var ed = input["ed"].ToString();
        var sd = input["sd"].ToString();
        var zt = input["zt"].ToString();
        DatasController controller = new DatasController();
        List<InstallModel> actValue = controller.GetInstalls(pids, Convert.ToDateTime(sd), Convert.ToDateTime(ed),
            int.Parse(zt));
        //验证
        bool result = NunitTestHelper.CompareObject(actValue, expValue, out message);
        Assert.IsTrue(result, message);
    }
}
/// <summary>
/// 测试获取指定Profile页面浏览次数前几的页面信息 - 单APP最热内容排行
/// </summary>
[TestFixture]
public class DatasController_GetPagesTest
{
    [Test, TestCaseSource(typeof (TestCaseFactoryMethod), "GetPagesTest")]
    public void WebApiGetPagesTest(JObject input, List<PageInfoModel> expValue)
    {
        string message;
        //
        var pids = input["pids"].ToString();
        var pn = input["pn"].ToString();
        DatasController controller = new DatasController();
        List<PageInfoModel> actValue = controller.GetPages(pids, int.Parse(pn));
        //验证
        //验证
        bool result = NunitTestHelper.CompareObject(actValue, expValue, out message);
        Assert.IsTrue(result, message);
    }

SCALA-单元测试环境搭建

• SCALA

Scala项目结构

IDEA安装Scala插件

配置Scalastyle

Sacla UnitTest

Scala 的单元测试框架主要有三个:ScalaTest、ScalaCheck和Spec2。其中ScalaTest是比较全面的测试框架。后面也主要使用ScalaTest编写Scala单元测试。以下介绍在各种构建环境下如何导入ScalaTest。同时我们公司开发测试都使用ScalaTest。不同的构建工具,导入ScalaTest的方式稍有区别。

<dependency>
  <groupId>org.scalatest</groupId>
  <artifactId>scalatest_2.11</artifactId>
  <version>3.0.1</version>
  <scope>test</scope>
</dependency>
libraryDependencies += "org.scalatest" % "scalatest_2.11" % "3.0.1" % "test"

编写ScalaUnitTest

选择测试风格

定义测试抽象类,推荐命名为:unitSpec

package com.mycompany.myproject

import org.scalatest._

abstract class UnitSpec extends FlatSpec with Matchers with
  OptionValues with Inside with Inspectors

使用自定义测试基类,编写测试类

package com.mycompany.myproject

import org.scalatest._

class MySpec extends UnitSpec {
  // Your tests here
}

测试类的命名约束

编写测试主题和测试

在FlatSpec中,用主题来表达一组验证相同功能的测试。用it来表示单个测试,测试可以写成:”A should B”, ”A must B”, ” A can B”, 后面跟in闭包表示需要被测试的代码块。

behavior of "A Stack (with one item)"

it should "be non-empty" in {}

it should "return the top item on peek" in {}

it should "not remove the top item on peek" in {}

it should "remove the top item on pop" in {}
"A Stack (with one item)" should "be non-empty" in {}

it should "return the top item on peek" in {}

it should "not remove the top item on peek" in {}

it should "remove the top item on pop" in {}
import collection.mutable.Stack
import org.scalatest._

class StackSpec extends FlatSpec {

  "A Stack" should "pop values in last-in-first-out order" in {
    val stack = new Stack[Int]
    stack.push(1)
    stack.push(2)
    assert(stack.pop() === 2)
    assert(stack.pop() === 1)
  }

  it should "throw NoSuchElementException if an empty stack is popped" in {
    val emptyStack = new Stack[String]
    assertThrows[NoSuchElementException] {
      emptyStack.pop()
    }
  }
}
assert(a == b || c >= d)
// Error message: 1 did not equal 2, and 3 was not greater than or equal to 4

assert(xs.exists(_ == 4))
// Error message: List(1, 2, 3) did not contain 4

assert("hello".startsWith("h") && "goodbye".endsWith("y"))
// Error message: "hello" started with "h", but "goodbye" did not end with "y"

assert(num.isInstanceOf[Int])
// Error message: 1.0 was not instance of scala.Int

assert(Some(2).isEmpty)
// Error message: Some(2) was not empty
val a = 5
val b = 2
assertResult(2) {
  a - b
}
val s = "hi"
try {
  s.charAt(-1)
  fail()
}
catch {
  case _: IndexOutOfBoundsException => // Expected, so continue
}
package org.scalatest.examples.flatspec.beforeandafter

import org.scalatest._
import collection.mutable.ListBuffer

class ExampleSpec extends FlatSpec with BeforeAndAfter {

  val builder = new StringBuilder
  val buffer = new ListBuffer[String]

  before {
    builder.append("ScalaTest is ")
  }

  after {
    builder.clear()
    buffer.clear()
  }

  "Testing" should "be easy" in {
    builder.append("easy!")
    assert(builder.toString === "ScalaTest is easy!")
    assert(buffer.isEmpty)
    buffer += "sweet"
  }

  it should "be fun" in {
    builder.append("fun!")
    assert(builder.toString === "ScalaTest is fun!")
    assert(buffer.isEmpty)
  }
}

运行ScalaUnitTest

ScalaTest支持多种方式运行测试,同样支持 Junit4 Runner方式运行,例子如下:

Run, Debug, Coverage

前提:安装IDEA scala 插件