0 单元测试原则
-
自动化:为了提高效率,单元测试应该是自动定期执行,这样完全自动化才能保证对系统有效回归验证
-
独立性:单元测试用例间不能相互调用,也不能依赖执行的先后次数
-
可重复: 不受外部环境影响,例如远程外部服务、MQ等。这是为了保证持续继承的可用性。
1 单元测试工具
工欲善其事,必先利其器。Java写单元测试常需要泳道下列工具:
- 测试框架:Junit
- 断言工具:AssertJ
- 模拟框架:Mockito
通过这些工具,我们可以对返回值、系统内部状态进行测试。如果有外部依赖,可以通过模拟外部框架,对外部依赖隔离,最终实现单元测试。
常见的模拟框架有
- Mockito
- PowerMock
- JMockit
Mockito使用样例如下:
依赖
<!-- https://mvnrepository.com/artifact/org.mockito/mockito-core -->
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>3.6.28</version>
<scope>test</scope>
</dependency>
模拟Iterator及期望的结果
public class Test {
@Before
public void setup() {
MockitoAnnotations.openMocks(this);
}
@Test
public void testIterator() {
Iterator iterator = Mockito.mock(Iterator.class);
Mockito.when(iterator.next()).thenReturn("1").thenReturn("2");
String result = iterator.next() + "/" +iterator.next();
Assert.assertEquals(result,"1/2");
}
}
2 Java Web应用分层测试方法
当前Java web应用常见结构分为三层,针对每层进行单侧需要使用不同的方法
- Controller
- Service
- DAO
Controller层:Controller依赖容器,一般测试时需要启动项目,因此单测执行时间长。因此可以考虑使用Spring-test来对Controller测试。借助MockMvc来进行Controller测试
Service层:Service通常会依赖Dao及RPC,因此构造环十分麻烦,也很难保证单侧可重复执行。因此Service的测试思路是通过Mock技术对外部依赖进行隔离。如下Mock RemoteService,并注入到DeepLocalService中,实现隔离,完成单测。
public interface RemoteService {
String get();
}
public class DeepLocalService implements LocalService{
private RemoteService remoteService;
@Override
public String print() {
return remoteService.get();
}
}
public class Test {
@InjectMocks
private DeepLocalService localService;
@Mock
private RemoteService remoteService;
@Before
public void setup() {
MockitoAnnotations.openMocks(this);
Mockito.when(remoteService.get()).thenReturn("test");
}
@Test
public void testLocalRemote() {
String result = localService.print();
Assert.assertEquals(result,"test");
}
}
DAO层:DAO层测试依赖数据库中的值,和环境相关,一旦环境变化,单测变无法重复执行。因此处理思路也是通过Mock技术,对数据库进行隔离。DAO的测试通要验证SQL正确性、数据库中结构是否正确等,所以要保证在真是的数据库环境,才能保证代码正确。这时可以考虑使用DBUnit来完辅助成这些工作。
3.让代码更容易测试
有时因为代码的复杂度高、结构不好,最终导致了单元测试的复杂度。不好通常是耦合度高,职责不明确。导致难以对代码的依赖部分模拟替换。 这部分需要重构代码,最终让代码更容易测试。
其次使用的Mock框架可能在功能上有欠缺,例如不支持static关键字等。这是也需要对代码进行简单调整,最终让系统更容易测试。
4.TDD测试驱动开发
为了更好进行测试保证质量,可以基于TDD来进行质量保证。
测试先行,通过测试逐个修复失败的测试用例,最终完成开发。
其过流程下:
5 参考书籍
- 单元测试的艺术
- 测试驱动开发的艺术
- 重构-改善既有代码的设计
- 敏捷软件开发 - 原则、模式与实践