本文主要介绍Python,java,go,C++,OC常用单元测试框架。用于了解各种语言单测。
一 python单元测试Pytest
1. Pytest主要功能
pytest是python的一种单元测试框架,同自带的Unittest测试框架类似,相比于Unittest框架使用起来更简洁,效率更高。
- 非常容易上手,入门简单,文档丰富,文档中有很多实例可以参考。
- 能够支持简单的单元测试和复杂的功能测试。
- 支持参数化。
- 能够执行全部测试用例,也可以挑选部分测试用例执行,并能重复执行失败的用例。
- 支持并发执行,还能运行由nose, unittest编写的测试用例。
- 方便、简单的断言方式。
- 能够生成标准的Junit XML格式的测试结果。
- 具有很多第三方插件,并且可以自定义扩展。
- 方便的和持续集成工具集成。
2. 安装
$ pip install -U pytest
pycharm运行pytest,需要修改该工程设置默认的运行器
3. Demo
import pytest
class Testcase001:
def setup(self):
print("setup: 每个用例开始前执行")
def teardown(self):
print("teardown: 每个用例结束后执行")
def test_001(self):
x = "hello"
assert "h" in x
def test_002(self):
a=10
assert a < 20
if __name__ == "__main__":
pytest.main(["-q", "test_d.py"])
3.1 测试用例setup和teardown
- 模块级(setup_module/teardown_module)开始于模块始末,全局的
- 函数级(setup_function/teardown_function)只对函数用例生效(不在类中)
- 类级(setup_class/teardown_class)只在类中前后运行一次(在类中)
- 方法级(setup_method/teardown_method)开始于方法始末(在类中)
- 类里面的(setup/teardown)运行在调用方法的前后
3.2 自定义测试用例的预置条件
firture相对于setup和teardown来说应该有以下几点优势
- 命名方式灵活,不局限于setup和teardown这几个命名
- conftest.py 配置里可以实现数据共享,不需要import就能自动找到一些配置
- scope=“module” 可以实现多个.py跨文件共享前置, 每一个.py文件调用一次
- scope=“session” 以实现多个.py跨文件使用一个session来完成多个用例
调用fixture三种方法
- 1.函数或类里面方法直接传fixture的函数参数名称
- 2.使用装饰器@pytest.mark.usefixtures()修饰
- 3.autouse=True自动使用
#conftest.py
import pytest
@pytest.fixture()
def login():
print("输入账号,密码先登录")
#conftest配置参数
def pytest_addoption(parser):
parser.addoption(
"--cmdopt", action="store", default="type1", help="my option: type1 or type2"
)
@pytest.fixture
def cmdopt(request):
return request.config.getoption("--cmdopt")
class Testcase001:
def test_001(self, login):
x = "hello"
assert "h" in x
3.3 测试用例参数化,可以实现测试用例参数化
3.4 命令行传参,需要在conftest.py添加命令行选项
3.5 assert断言
3.6 pytest.mark.skip可以标记无法在某些平台上运行的测试功能,或者您希望失败的测试功能
3.7 函数传参和fixture传参数request,如登录函数(使用不同账号)
二 java单元测试JUnit4
1. JUnit4主要功能
1.主流Java单元测试框架,注解支持
2.测试分为三级:test,test case,test suite
3.丰富的断言机制,判断测试通过/失败
4.支持不同级别的公共数据准备(Fixture)
2. 插件安装
第一步:在你需要添加Junit的工程下,右键工程
第二步:点击configure Build Path…
第三步:点击Add Library:JUnit4,JUnitGenerator V2.0
JUnitGenerator V2.0: 修改Output Path为:${SOURCEPATH}/../../test/java/${PACKAGE}/${FILENAME},
Default Template选择JUnit 4。
3. dome
//被测试函数
public class Demo {
public int add(int a ,int b){
return a + b ;
}
}
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import static org.junit.Assert.*;
public class DemoTest {
static Demo d;
@Before
public void setUp() throws Exception {
System.out.println("静态方法");
d = new Demo();
}
@After
public void tearDown() throws Exception {
System.out.println("执行完了");
}
@Test
public void add () {
Assert.assertEquals(10, d.add(4, 6)) ;
System.out.println("测试中");
}
//异常测试
@Test(expected = ArithmeticException.class)
public void divisionWithException() {
int i = 1/0;
}
//忽略测试
@Ignore("Not Ready to Run")
@Test
public void divisionWithException1() {
System.out.println("Method is not ready yet");
}
//时间限制
@Test(timeout = 1000)
public void infinity() {
while (true);
}
}
import org.junit.runner.RunWith;
import org.junit.runners.Suite.SuiteClasses;
import org.junit.runners.Suite;
@RunWith(Suite.class) //声明套件运行器
@SuiteClasses({DemoTest.class}) //将需要一起测试的类放进来
public class SuiteTest {
/*
* 测试套件就是组织测试类一起运行的
* 写一个作为测试套件的入口类,这个类里不需要包含其他的方法
* 1.更改测试运行器Suite.class
* 2.将要测试的类作为数组传入到Suite.SuiteClasses({})
*/
}
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import java.util.Arrays;
import java.util.Collection;
//“@RunWith”和“@Parameter”用于提供单元测试的参数值,@Parameters必须返回List [],参数将作为参数传入类构造函数。
@RunWith(value = Parameterized.class)
public class ParameterizedTest {
private int number;
public ParameterizedTest(int number) {
this.number = number;
}
@Parameterized.Parameters
public static Collection<Object[]> data() {
Object[][] data = new Object[][] { { 1 }, { 2 }, { 3 }, { 4 } };
return Arrays.asList(data);
}
@Test
public void pushTest() {
System.out.println("Parameterized Number is : " + number);
}
}
4. TestNG
TestNG是一个开源自动化测试框架,其灵感来自JUnit和NUnit,TestNG还涵盖了整个核心的JUnit4功能,但引入了一些新的功能,使其功能更强大,使用更方便。
优势:支持依赖测试方法,并行测试,负载测试,局部故障;灵活的插件API;支持多线程测试;
三 go单元测试
1 Go对单元测试的原生支持
1.1 testing——Go内置的单元测试库。
//被测试函数
func addUpper(n int) int {
res := 0
for i :=1; i <= n; i++ {
res += i
}
return res
}
//测试用例文件名必须以_test.go结尾,测试用例函数必须以Test开头,一般来说就是Test+被测试的函数名
//TestXxx(t *testing.T)的形参必须是*testing.T
func TestAddUpper(t *testing.T) {
//调用
res := addUpper(10)
if res != 55 {
//输出日志然后退出
t.Fatalf("AddUpper(10) 执行错误,期望值=%v 实际值=%v\n", 55, res)
}
//如果正确,输出日志
t.Logf("AddUpper(10) 执行正确")
}
1.2 TestMain
在写测试时,有时需要在测试之前或之后进行额外的设置(setup)或拆卸(teardown);有时,测试还需要控制在主线程上运行的代码。为了支持这些需求,testing 提供了 TestMain 函数。1.3 httptest——HTTP测试辅助工具
//func TestMain(m *testing.M)
func TestMain(m *testing.M) {
fmt.Println("begin")
m.Run()
fmt.Println("end")
}
1.3 httptest——HTTP测试辅助工具
Go 标准库专门提供了 httptest 包专门用于进行 http Web 开发测试。提供了一个 http.ReponseWriter
接口的实现结构:httptest.ReponseRecorder
,通过它可以得到一个http.ReponseWriter
,并以此来接收服务器返回的响应包。
2 gocheck
- 丰富了单元测试常用的 assert 断言,判断动词deep multi-type 对比,字符串比较以及正则匹配
- 测试用例组织集合方面按suite组织测试用例,支持suite级别的 setup() 和 teardown()
- 对于临时文件支持创建、删除临时文件和目录。
import (
. "gopkg.in/check.v1"
"testing"
)
func Test(t *testing.T) { TestingT(t) } //继承testing的方法,可以直接使用go test命令运行
type MySuite struct{} //创建测试套件结构体
var _ = Suite(&MySuite{})
func (s *MySuite) TestAddUpper(c *C) { //声明TestHelloWorld方法为MySuite套件的测试用例
c.Assert(addUpper(10), Equals, 55)
}
3 goconvey
- 直接集成go test
- 可以管理和运行测试用例
- 提供了丰富的断言函数
- 支持很多 Web 界面特性
import (
"testing"
. "github.com/smartystreets/goconvey/convey"
)
func TestEqual(t *testing.T) {
//每一个Convey语句对应一个测试用例
Convey("TestEqual should return true ", t, func() {
//Convey函数的第三个参数闭包的实现中通过So函数完成断言判断
So(addUpper(10), ShouldEqual, 55)
})
//针对想忽略但又不想删掉或注释掉某些断言操作,GoConvey提供了Convey/So的Skip方法
//当存在SkipConvey或SkipSo时,测试日志中会显式打上"skipped"形式的标记
//SkipConvey函数表明相应的闭包函数将不被执行
//SkipSo函数表明相应的断言将不被执行
Convey("TestEqual should return true", t, func() {
So(addUpper(10), ShouldEqual, 55)
SkipSo(addUpper(10), ShouldEqual, 55)
})
SkipConvey("TestEqual should return true", t, func() {
So(addUpper(10), ShouldEqual, 55)
SkipSo(addUpper(10), ShouldEqual, 55)
})
}
//Convey语句可以无限嵌套,以体现测试用例之间的关系。只有最外层的Convey需要传入*testing.T类型的变量t。
//以测试函数为单位多个测试用例集中显示的形式
func TestStringSliceEqual(t *testing.T) {
Convey("TestStringSliceEqual", t, func() {
Convey("should return true", func() {
So(addUpper(100), ShouldNotEqual, 55)
})
})
}
4 testify
安装:
5 mock/stub方案
5.1 识别依赖
- 网络依赖——函数执行依赖于网络请求,比如第三方http-api,rpc服务,消息队列等
- 数据库依赖
- I/O依赖(文件)
- 还未开发完成的功能模块
5.2 gostub
特性:可以为全局变量、函数、过程打桩,比gomock轻量,不需要依赖接口
缺陷:对项目源代码有侵入性,即被打桩方法必须赋值给一个变量,只有以这种形式定义的方法才能别打桩
5.3 gomock
特性:golang官方开发维护的接口级别的mock方案,包含了GoMock包和mockgen工具两部分,其中GoMock包完成对桩对象生命周期的管理,mockgen工具用来生成interface对应的Mock类源文件。
缺陷:只有以接口定义的方法才能mock,需要用mockgen生成源文件,然后用gomock去实现自己想要的数据。
5.4 gomonkey
特性:可以为全局变量、函数、过程、方法打桩,同时避免了gostub对代码的侵入。
缺陷:对inline函数打桩无效,不支持多次调用桩函数(方法)而呈现不同行为的复杂情况。
总结:全局变量可通过GoStub框架打桩,interface可通过GoMock框架打桩,过程/函数/方法通过Monkey框架打桩
四 C++单元测试
1. gotest概述
对代码进行单元测试。接口测试也是常见的。可以对类,函数,代码分支进行测试。
2. 安装
git clone https://github.com/google/googletest.git
cd googletest
mkdir -p build && cd build
cmake ..
make
sudo make install
3. DEMO
# CMakeList.txt 在文件中添加头文件和链接库文件,并将链接库文件与目标文件进行链接
# cmake最低版本号要求
cmake_minimum_required(VERSION 3.15)
# 项目名
project(demo_gtest)
set(CMAKE_CXX_STANDARD 17)
# 添加头文件
include_directories(
${CMAKE_CURRENT_SOURCE_DIR}/include
${CMAKE_CURRENT_SOURCE_DIR}/googletest/include
)
# 添加下级目录,生成.a文件
add_subdirectory( ./googletest)
# 添加连接库
link_directories(
${CMAKE_CURRENT_SOURCE_DIR}/lib
${CMAKE_CURRENT_SOURCE_DIR}/googletest
)
set(SOUCE_FLIES
main.cpp
lib/add.cpp
include/add.h)
# 指定可执行文件的生成位置
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/bin")
# 指定生成目标
add_executable(Testadd ${SOUCE_FLIES} include/add.h lib/add.cpp)
# 将目标文件与库文件进行链接
target_link_libraries(Testadd gtest)
#lib/add.h
#ifndef DEMO_GTEST_ADD_H
#define DEMO_GTEST_ADD_H
int add(int n1,int n2);
#endif //DEMO_GTEST_ADD_H
#include/add.cpp
int add(int n1,int n2)
{
return n1+n2;
}
#include <iostream>
#include "lib/add.h"
#include "gtest/gtest.h"
TEST(TestCase,test1 ){
ASSERT_EQ(12,add(4,8));
}
TEST(TestCase,test2){
EXPECT_EQ(5,add(2,3));
}
TEST(TestCase,test3){
EXPECT_EQ(3,add(1,2));
}
int main(int argc, char **argv)
{
testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
3.1 断言
gtest中,断言的宏可以理解为分为两类,一类是ASSERT系列,一类是EXPECT系列。一个直观的解释就是:
1.ASSERT_* 系列的断言,当检查点失败时,退出当前函数(注意:并非退出当前案例)。
2.EXPECT_* 系列的断言,当检查点失败时,继续往下执行。
3.2 事件机制
gtest提供了多种事件机制,非常方便我们在案例之前或之后做一些操作。总结一下gtest的事件一共有3种:
1.全局的,所有案例执行前后。
2.TestSuite级别的,在某一批案例中第一个案例前,最后一个案例执行后。
3.TestCase级别的,每个TestCase前后。
3.3 参数化
gtest提供了一个灵活的参数化测试的方案
4. Gmock
Google 的 gtest 框架自带一个 google mock,用于对代码进行 mock 操作。对于测试时难以构造的输入输出条件(例如 rpc 请求,从 db 获取数据),可以通过 mock 的手段进行构造。
5. HMock
Hmock解决了后台单元测试gtest/gmock中静态函数、一般函数、全局函数、函数模版以及非虚函数模版类等不能被mock问题。
五 Objective-C单元测试
现在比较流行的就属Apple自带的XCTest和第三方的GHUnit。区别:XCTest,与Xcode深度集成。 GHUnit:集成度不如XCTest,安装麻烦。但是有自己的GUI界面。 Github上的一些知名的开源库都用的是XCTest,Github上的case例子可以参考,有利于使用。
#import <XCTest/XCTest.h>
@interface TestDemoTests : XCTestCase
@end
@implementation TestDemoTests
- (void)setUp {
// Put setup code here. This method is called before the invocation of each test method in the class.
}
- (void)tearDown {
// Put teardown code here. This method is called after the invocation of each test method in the class.
}
- (void)testExample {
// This is an example of a functional test case.
// Use XCTAssert and related functions to verify your tests produce the correct results.
NSLog(@"test2");
XCTAssert(1, @"Can not be zero");
}
- (void)testPerformanceExample {
// This is an example of a performance test case.
[self measureBlock:^{
// Put the code you want to measure the time of here.
XCTAssertEqual(2, 2, @"a1 = a2 shoud be true");
}];
}