testtest
1、单元测试
针对的是代码中的每个类,一个类认为是一个代码组件,对每个代码组件都编写一个单元测试类。这个单元测试类中会有多个方法,通常至少对要测试的类中的每个方法都编写一个对应的测试方法。
单元测试,面向的是代码组件的级别,它是最小最细粒度的测试单元
通常而言,通过单元测试检查出来的bug是最多的,所以它是位于测试金字塔模型的最底端
单元测试,需要每个RD对自己编写的代码自己去写单元测试,一般是使用JUnit框架,如果你使用了一些其他的框架,Spring,Spring MVC,都有对应的跟JUnit整合起来进行单元测试的一些框架,逻辑判断的Hamcrest框架,mock对象的Mockito框架。
初步的一些规范:
- 单元测试类,必须以test来结尾,以要测试的类来打头
- 针对每个类,一般都要写一个单元测试类来进行测试
- 单元测试中的每个方法,针对的是被测试类中的每个方法,方法以test打头,跟上要测试的方法名称.
下面给出单元测试的一个基本的例子:
public class HelloWorld {
public String sayHello(String name) {
return "hello, " + name;
}
}
public class HelloWorldTest {
private HelloWorld helloWorld = new HelloWorld();
@Test
public void testSayHello() {
String name = "leo";
String result = helloWorld.sayHello(name);
assertEquals("hello, leo", result);
}
}
下面是使用测试替身的一个例子
public class HelloWorld {
private GreetingMessageService greetingMessageService;
public String sayHello(String name) {
greetingMessageSerivce.save(name);
return "hello, " + name;
}
public void setGreetingMessageService(GreetingMessageService greetingMessageService) {
this.greetingMessageService = greetingMessageService;
}
public GreetingMessageService getGreetingMessageService() {
return greetingMessageService;
}
}
public class GreetingMessageServiceImpl implements GreetingMessageService {
private GreetingMessageDAO greetingMessageDAO;
public void save(String name) {
greetingMessageDAO.save(name);
}
}
重要的原则:单元测试中,你测试一个类,就只能针对这一个类中的代码来进行测试。如果这个类依赖了其他的类,必须用测试替身将依赖的类和要测试的类隔离开来。为什么呢?记住,单元测试是什么,单元测试其实是最小的测试单位,一个单元测试类就负责测试一个代码组件(一个类),如果这个类依赖了其他的类。那么你就自己模拟一些测试替身,注入到要测试的类中去,将要测试的类和依赖的类隔离开来。
避免说,要测试的类和依赖的类耦合在一起测试,互相如果有bug会互相影响
public class GreetingMessageServiceStub implements GreetingMessageService {
public void save(String name) {
System.out.println("received a message: " + name);
}
}
public class HelloWorldTest {
private HelloWorld helloWorld;
@Before
public void setup() {
this.helloWorld = new HelloWorld();
GreetingMessageService greetingMessageService = new GreetingMessageServiceStub();
this.helloWorld.setGreetingMessageService(greetingMessageService);
}
@Test
public void testSayHello() {
String name = "leo";
String result = helloWorld.sayHello(name);
assertEquals("hello, leo", result);
}
@After
public void teardown() {
this.helloWorld = null;
}
}
fake对象的示例
public class UserDAOFake implements UserDAO {
private Map<Long, User> mockDB = new HashMap<Long, User>();
public void save(User user) {
mockDB.put(user.getUserId(), user);
}
public void update(User user) {
mockDB.put(user.getUserId(), user);
}
public void remove(Long userId) {
mockDB.remove(userId);
}
public User getUserById(Long userId) {
return mockDB.get(userId);
}
}
spy对象的示例
public class GreetingMessageServiceSpy implements GreetingMessageService {
private String _name;
public void save(String name) {
this._name = name;
System.out.println("received a message: " + name);
}
public boolean received(String name) {
return _name.equals(name);
}
}
public class HelloWorldTest {
private HelloWorld helloWorld;
@Before
public void setup() {
this.helloWorld = new HelloWorld();
GreetingMessageService greetingMessageService = new GreetingMessageServiceSpy();
this.helloWorld.setGreetingMessageService(greetingMessageService);
}
@Test
public void testSayHello() {
String name = "leo";
String result = helloWorld.sayHello(name);
assertEquals("hello, leo", result);
GreetingMessageServiceSpy greetingMessageService =
(GreetingMessageServiceSpy)helloWorld.getGreetingMessageService();
assertTrue(greetingMessageService.received(name));
}
@After
public void teardown() {
this.helloWorld = null;
}
}
mock对象的伪代码示例
public class HelloWorldTest {
private HelloWorld helloWorld;
@Before
public void setup() {
this.helloWorld = new HelloWorld();
GreetingMessageService greetingMessageSerivce = mock(GreetingMessageSerivce.class);
when(greetingMessageSerivce.save("leo")).thenReturn("success");
this.helloWorld.setGreetingMessageService(greetingMessageService);
}
@Test
public void testSayHello() {
String name = "leo";
String result = helloWorld.sayHello(name);
assertEquals("hello, leo", result);
}
@After
public void teardown() {
this.helloWorld = null;
}
}
重要原则:单元测试绝对不能依赖任何的外部基础设施,比如说mysql、redis、rabbitmq,绝对不能依赖这些东西来写单元测试。如果要依赖的话,直接注入测试替身,用模拟的行为来替代掉。
重要规范:单元测试覆盖率,要保证你写的单元测试覆盖了足够多的代码,保证覆盖的代码至少达到70%,对核心模块的代码要覆盖100%
一个好的单元测试要符合的一个规范和规则
- 什么是单元测试?
- 直接裸奔不写单元测试的弊端
如果裸奔不写单元测试的话,就直接意味着,这个RD根本就没有对自己编写的代码负责;
最基本的单元测试都没有,凭什么让人相信你的代码是经过测试的呢?
写更多的单元测试对代码质量提高的稳定曲线.
写更多的单元测试对代码设计质量提高的稳定曲线 - 优秀的单元测试有哪些特质?
可读性
良好的测试代码结构
3.精准的测试名称
测试代码的可重复性 - 自动化单元测试的3大工具
测试框架:JUnit来测试,加上其他一些辅助性质的测试框架
运行单元测试的自动化构建:基于maven的插件,mvn test,自动把所有的单元测试都自动化跑一遍
测试替身:主要使用的是Mockito框架,模拟出来各种各样实现某个接口的类,每个方法的模拟的行为是什么 - 测试替身
测试替身的几大作用:
隔离要测试的代码组件:将测试替身对象传入要测试的组件
加快测试执行速度:替换复杂耗时的代码执行
有的时候会有这样的一种情况,比如你要测试的是类A,类A依赖了类B,类B中的代码非常复杂,执行了大量的数据库读写操作, 导致类B的那个方法跑起来是很慢的
就是用测试替身代替类B,将类B中的方法实现为非常简单的一些打印日志的模拟的实现就可以了,因为我们重点要测试的是类A,不是类B
测试替身的一个效果,就是可以加速类A中的方法的执行速度,也就可以加快我们的单元测试运行的速度
让代码行为变得足够稳定:将随机行为改为固定行为
1.类A依赖了类B,类B中会随机生成一个随机数,根据随机数来执行一些行为, 这就会导致类A每次调用类B,执行的结果是随机的,不够稳定