首先,官网:
https://www.doczj.com/doc/027052470.html,/p/powermock/
先做好心理准备,这个开源工具的官网基本上没啥文字说明。但是可以下载源代码,里面有一些示例测试用例。
当你的领导对你说,UT的代码覆盖率要达到100%!!
你会觉得这人疯了。
但是现在有了powermock,100%就成为the goal you can reach!!!
powermock将以往我们认为无法完成的任务变成了可能。
打开Powermock的官网,我们可以看到Usage:
1. Mocking static methods
2. Mocking final methods or classes
3. Mocking private methods
4. Mock construction of new objects
5. Partial Mocking
6. Replay and verify all
7. Mock Policies
8. Test listeners
9. More to come...
可见,Powermock专门用来应付一些奇怪的测试需求,例如mock private方法,mock 静态方法,mock final方法。
这些需求传统而言,都是不需要,不应该测试的。
下面我们就来举一个例子,看看其他工具不能解决的怪异问题,powermock是怎么实现的。对象是在方法内部被实例化的
我们来看一个简单的类,然后考察如何完成对应的测试。
public class SayHi {
public String sayHi(String a, String b){
Adder adder = new Adder(); //实例化了一个adder,作用就是将两个字符串加在一起。String result = "";
result = adder.add(a, b);
return result;
}
}
public class Adder {
public String add(String a, String b){
return a + " " + b;
}
}
如果我们要测试SayHi这个类,很简单:
public class SayHiTest extends TestCase {
@Test
public void testSayHi() {
SayHish = new SayHi();
assertTrue(sh.sayHi("abc", "def").equalsIgnoreCase("abcdef"));
}
}
通过运行Cobertura,可以看到这个类的测试覆盖率是100%。(关于Cobertura,我准备过几天有时间写篇文章介绍一下)
现在,高难度的来了~~稍微更改一下Adder类和SayHi类:
public class Adder throws Exception{
public String add(String a, String b){
return a + " " + b;
}
}
public class SayHi {
public String sayHi(String a, String b){
Adder adder = new Adder();
String result = "";
try {
//由于Adder类抛出了一个Exception,导致在使用这个类时必须加上try/catch。
result = adder.add(a, b);
} catch (Exception e) {
result = "Failed";
}
return result;
}
}
现在再看看Cobertura,可以看到这个类的测试覆盖率是75%。因为在现有的UT中,没有对异常处理部分的测试。换言之,如果要想测试覆盖率达到100%,就必须在UT中使Adder 抛出异常,进而测试代码是否做了正确的异常处理。
此时应该是mock出场的时候了,我们想做的事情是,用mock对象代替真实的Adder,强行让mock对象抛出异常,从而进一步测试。
例如在这个test case中,我们就希望创建一个mockAdder对象,代替Adder。在调用mockAdder.add()时,一定会抛出异常,进而进入到异常处理部分,使运行结果为failed。
这时真正的问题出现了:
在SayHi这个类的方法sayHi中,实例化了Adder adder = new Adder(); 即,adder这个对象不是inject进来的,而是直接在方法内部实例化出来的。
在Mockito的介绍中我已经提到了,要用mock测试,前提条件就是如何用mock对象覆盖掉真实对象,让mock对象代替真实对象做出我们希望的动作。
在Mockito介绍的示例中,我们都假定源代码提供了get/set方法,因此我们很容易使用set 方法,将mock对象传递进去。也就是说,一个易于被测试的源代码应该是:
public class SayHi {
Adder adder;
public String sayHi(String a, String b){
adder = getAdder();
String result = "";
try {
result = adder.add(a, b);
} catch (IOException e) {
e.printStackTrace();
}
return result;
}
public Adder getAdder(){
return adder;
}
public void setAdder(Adder a){
this.adder = a;
}
}
而对于之前的SayHi类,却无法将mock对象传递进去。100%成为了一个不可能完成的任务。
此时,我们的选择之一是修改源代码。在面向对象的语言中,我们一直强调灵活,独立的代码结构。如果一个类难于被测试,这很可能是代码结构不好的象征。
很明显sayHi这个方法依赖于Adder这个类,这种写法很不灵活。很容易由于外围的更改导致不得不修改这个类的代码。
但是由于种种原因,也许我们不愿意修改源代码。
此时就进入了本文的正题~~~~~powermock如何将不可能变为可能。
import static org.junit.Assert.*;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import static org.mockito.Mockito.*;
//需要注意的是,powermock依赖于JUnit或TestNG,Mockito或EasyMock。
//这里我使用的是JUnit+Mockito,所以需要import上面的这些类。
@RunWith(PowerMockRunner.class)
@PrepareForTest( { SayHi.class })
//这两句annotation很重要,否则powermock不会生效的。
public class SayHiTest {
@Test
public void testSayHi() throws Exception {
Adder adder = mock(Adder.class); //mock出一个模拟的对象,用于代替真实的adder。when(adder.add(anyString(), anyString())).thenThrow(new Exception()); //Stub虚拟对象的行为,即当调用模拟对象的add方法时,抛出异常。到这里使用的都是Mockito的功能。
PowerMockito.whenNew(Adder.class).withNoArguments().thenReturn(adder);//这里powerMock开始发挥作用:当Add.class被实例化的时候,强制使用模拟对象adder代替代码中被实例化出来的对象。
SayHish = new SayHi();
assertTrue(sh.sayHi("abc", "def").equalsIgnoreCase("failed"));//这里我们看到了希望的效果:异常处理中的语句result = "Failed";被执行了
}
}
在这里很high的去看一下代码覆盖率:100%~~yes!!
Powermock为什么能将不可能变为可能,我们不需要深究,大概的实现方法是:
PowerMock uses a custom classloader and bytecode manipulation to enable mocking of static methods, constructors, final classes and methods, private methods, removal of static initializers and more.
简单的说,powermock是通过修改字节码.class file + 用户自定义类装载器(class loader是JVM的组件之一)来实现的。
你基本上可以认为,powermock通过修改字节码文件,修改了你的源代码,从而用mock 对象代替了源代码中调用的对象。
以往很难被测试的情况,如private方法等,现在都可以被测试了。大家去参考官网的文档吧~
C语言符号意义 Company Document number:WTUT-WT88Y-W8BBGB-BWYTT-19998
C语言符号意义大全 32个关键字及其含义: auto :声明自动变量一般不使用 double :声明双精度变量或函数 int:声明整型变量或函数 struct:声明结构体变量或函数 break:跳出当前循环 else :条件语句否定分支(与 if 连用) long :声明长整型变量或函数 switch :用于开关语句 case:开关语句分支 enum :声明枚举类型 register:声明积存器变量 typedef:用以给数据类型取别名(当然还有其他作用) char :声明字符型变量或函数 extern:声明变量是在其他文件正声明(也可以看做是引用变量)return :子程序返回语句(可以带参数,也看不带参数)union:声明联合数据类型 const :声明只读变量 float:声明浮点型变量或函数 short :声明短整型变量或函数 unsigned:声明无符号类型变量或函数
continue:结束当前循环,开始下一轮循环 for:一种循环语句(可意会不可言传) signed:生命有符号类型变量或函数 void :声明函数无返回值或无参数,声明无类型指针(基本上就这三个作用) default:开关语句中的“其他”分支 goto:无条件跳转语句 sizeof:计算数据类型长度 volatile:说明变量在程序执行中可被隐含地改变 do :循环语句的循环体 while :循环语句的循环条件 static :声明静态变量 if:条件语句 C语言中像%D&%f符号的作用说一下 C语言中的符号运算符的种类C语言的运算符可分为以下几类:1.算术运算符用于各类数值运算。包括加(+)、减(-)、乘(*)、除(/)、求余(或称模运算,%)、自增(++)、自减(–)共七种。 2.关系运算符用于比较运算。包括大于(>)、小于(<)、等于(==)、大于等于(>=)、小于等于(<=)和不等于(!=)六种。 3.逻辑运算符用于逻辑运算。包括与(&&)、或(||)、非(!)三种。 4.位操作运算符参与运算的量,按二进制位进行运算。包括位与(&)、位或(|)、位非(~)、位异或(^)、左移(<<)、右移(>>)六种。
曾经碰到过让你迷惑不解、类似于int * (* (*fp1) (int) ) [10];这样的变量声明吗?本文将由易到难,一步一步教会你如何理解这种复杂的C/C++声明。 我们将从每天都能碰到的较简单的声明入手,然后逐步加入const修饰符和typedef,还有函数指针,最后介绍一个能够让你准确地理解任何C/C++声明的“右左法则”。 需要强调一下的是,复杂的C/C++声明并不是好的编程风格;我这里仅仅是教你如何去理解这些声明。注意:为了保证能够在同一行上显示代码和相关注释,本文最好在至少1024x768分辨率的显示器上阅读。 让我们从一个非常简单的例子开始,如下: 这个应该被理解为“declare n as an int”(n是一个int型的变量)。接下去来看一下指针变量,如下: 这个应该被理解为“declare p as an int *”(p是一个int *型的变量),或者说p是一个指向一个int型变量的指针。我想在这里展开讨论一下:我觉得在声明一个指针(或引用)类型的变量时,最好将*(或&)写在紧靠变量之前,而不是紧跟基本类型之后。这样可以避免一些理解上的误区,比如: 再来看一个指针的指针的例子: 理论上,对于指针的级数没有限制,你可以定义一个浮点类型变量的指针的指针的指针的指针,再来看如下的声明: 这里,p被声明为一个包含5个元素(int类型的指针)的数组。另外,我们还可以在同一个声明中混合实用*和&,如下:
注:p1是一个int类型的指针的指针;p2是一个int类型的指针的引用;p3是一个int类型引用的指针(不合法!);p4是一个int类型引用的引用(不合法!)。 const修饰符 当你想阻止一个变量被改变,可能会用到const关键字。在你给一个变量加上const修饰符的同时,通常需要对它进行初始化,因为以后的任何时候你将没有机会再去改变它。例如: 上述两个变量n和m其实是同一种类型的——都是const int(整形恒量)。因为C++标准规定,const关键字放在类型或变量名之前等价的。我个人更喜欢第一种声明方式,因为它更突出了const修饰符的作用。当const与指针一起使用时,容易让人感到迷惑。例如,我们来看一下下面的p和q的声明: 他们当中哪一个代表const int类型的指针(const直接修饰int),哪一个代表int类型的const指针(const直接修饰指针)?实际上,p和q都被声明为const int类型的指针。而int类型的const指针应该这样声明: 这里,p和q都是指向const int类型的指针,也就是说,你在以后的程序里不能改变*p的值。而r是一个const指针,它在声明的时候被初始化指向变量n (即r=&n;)之后,r的值将不再允许被改变(但*r的值可以改变)。
指 针 ★指针的重要性 表示一些复杂的数据结构 快速传递数据 使函数返回一个以上的值 能直接访问硬件 能方便处理字符串 是理解面向对象语言中引用的基础 总结:指针是C 语言的灵魂 ★指针的定义 ☆地址 内存单元的编号 从零开始的非负整数 范围:4G ☆指针 1.指针就是地址,地址就是指针 2.指针变量是存放地址的变量 3.指针和指针变量是两个不同的概念 4.叙述时通常把指针变量简称为指针,实际它们含义不一样 5.指针的本质就是一个操作受限的非负整数 ★指针的分类 ☆基本类型指针(重要) #include<> int main(void) { int *p; 果一个指针变量指向了某个普通变量,则*指针变量 完全等同于 普通变量 例:若p 指向i ,则*p=i (*p 和i 可互相替换) p=&ch;法 2.定义指针变量 Int*p; 针运算符 该运算符放在已经定义好的指针变量的前面 如果p 是一个已经定义好的指针变量 则*p 表示以p 的内容为地址的变量 ?如何通过被调函数修改主调函数普通变量的值 1.实参必须为该普通变量的地址 &... 2.形参必须为指针变量 *... 3.在被调函数中通过 *形参名=...... 的方式就可以修改主调函数相关变量的值 例子: 经典指针程序:互换数值 形参和实参是不同的变量,修改形参不会改变实参 ?指针常见错误 #include<> #include<> void huhuan (int a, int b ) { int t; t=a; a=b; b=t; #include<> void huhuan2(int *p, int *q ) { int *t;//如果要互换p 和q 的值, 则t 必须是int*,不能是int t=p; p=q; #include<> void huhuan3(int *p, int*q ) //形参的名字是p 和q ,接收实参数据的是p 和q ,而不是*p 和*q { int t;//如果要互换*p 和*q 的值, 则t 必须是int ,不能是int* t=*p;//p 是int*,*p 是int Int f(int i,int j) { return 100; // return 88;error } Int main (void) { Int a=3,b=5; a=f(a,b); b=f(a,b); } 只能返回一个值 # include <> Void g(int*p,int*q) { *p=1; *q=2; } Int main(void) { Int a=3,b=5; g(&a,&b); Printf(“%d%d\n ”,a,b); Return 0; } 指针使函数返回一个以上的值