问题简单描述
针对单例模式的测试类的add,remove,clear函数进行单元测试,是对监听器列表的操作
具体情况
- 被测试类Java代码
public class FordAmbientLightManager extends FordCommonManager {
private static final String TAG = "FordAmbientLightManager";
private static FordAmbientLightManager manager;
private AmbientLightSettingRsp ambientLightSettingRsp;
private List<IAmbientLightChange> mIAmbientLightChangeListeners;
//单例模式,接口
public static synchronized FordAmbientLightManager getInstance(Context context) {
if (null == manager) {
manager = new FordAmbientLightManager(context);
}
return manager;
}
//初始化函数
public FordAmbientLightManager(Context context) {
super(context, APPID_ID_1021);
mIAmbientLightChangeListeners = new ArrayList<>();
addIResponseCallbackListener(mCallback);
addISynchronizationInterfaceListener(mISynchronizationInterface);
}
//监听器接口
public interface IAmbientLightChange {
void onAmbientLightChange(int type, int value);
}
//添加监听器
public void addIAmbientLightChangeListeners(IAmbientLightChange listener) {
if (mIAmbientLightChangeListeners != null) {
mIAmbientLightChangeListeners.add(listener);
}
}
//移除监听器
public void removeIAmbientLightChangeListeners(IAmbientLightChange listener) {
if (mIAmbientLightChangeListeners != null) {
mIAmbientLightChangeListeners.remove(listener);
}
}
//清空监听器
public void clearIAmbientLightChangeListeners() {
if (mIAmbientLightChangeListeners != null) {
mIAmbientLightChangeListeners.clear();
}
}
}
- 问题具体分析
mIAmbientLightChangeListeners是私有的一个IAmbientLightChange列表,对add,remove,clear函数测试的主要难点是如何断言,并且每一个函数都有两个条件需要判断,需要分成两种情况分别进行测试 - 具体测试代码
//test类初始化部分
class FordAmbientLightManagerUT extends BaseUnitTest {
private FordAmbientLightManager fordAmbientLightManager;
private Context context;
private AutoCloseable closeable;
@BeforeAll
public static void beforeAll() {
mockStatic(Log.class);
mockStatic(Car.class);
}
@BeforeEach
public void beforeEach() {
closeable = MockitoAnnotations.openMocks(this);
Context context = Mockito.mock(Context.class);
Mockito.when(context.getApplicationContext()).thenReturn(context);
fordAmbientLightManager = FordAmbientLightManager.getInstance(context);
}
@AfterEach
public void afterEach() throws IllegalAccessException, NoSuchFieldException {
try {
closeable.close();
} catch (Exception e) {
throw new RuntimeException(e);
}
Class c = fordAmbientLightManager.getClass();
Field manager = c.getDeclaredField("manager");
manager.setAccessible(true);
manager.set(fordAmbientLightManager, null);
}
//具体测试部分
//监听器列表为空:验证函数调用不抛异常
@Test
public void testAddIAmbientLightChangeListeners_mIAmbientLightChangeListenersIsNull() throws NoSuchFieldException, IllegalAccessException {
//prepare
//使用反射设置私有变量值
Field mIAmbientLightChangeListeners = getField(fordAmbientLightManager, "mIAmbientLightChangeListeners");
setField(mIAmbientLightChangeListeners, fordAmbientLightManager, null);
//assert
assertDoesNotThrow(() -> {
fordAmbientLightManager.addIAmbientLightChangeListeners(Mockito.mock(FordAmbientLightManager.IAmbientLightChange.class));
});
}
//添加监听器且监听器列表不为空
@Test
public void testAddIAmbientLightChangeListeners_mIAmbientLightChangeListenersIsNotNull() throws NoSuchFieldException, IllegalAccessException {
//PREPARE
//创建一个列表(已经是非空),利用反射设置私有变量值
FordAmbientLightManager.IAmbientLightChange mockListeners =Mockito.mock(FordAmbientLightManager.IAmbientLightChange.class);
List<FordAmbientLightManager.IAmbientLightChange> test_list = new ArrayList<>();
Field mIAmbientLightChangeListeners_Listeners = getField(fordAmbientLightManager, "mIAmbientLightChangeListeners");
setField(mIAmbientLightChangeListeners_Listeners, fordAmbientLightManager, test_list);
//INVOKE
fordAmbientLightManager.addIAmbientLightChangeListeners(mockListeners);
//VERIFY
Field mIAmbientLightChangeListeners_test = getField(fordAmbientLightManager, "mIAmbientLightChangeListeners");
List<FordAmbientLightManager.IAmbientLightChange> test = (List<FordAmbientLightManager.IAmbientLightChange>) mIAmbientLightChangeListeners_test.get(fordAmbientLightManager);
//验证生成的列表中含有我们想添加的监听器
assertTrue(test.contains(mockListeners));
}
//监听器列表为空:验证函数调用不抛异常
@Test
public void testRemoveIAmbientLightChangeListeners_mIAmbientLightChangeListenersIsNull() throws NoSuchFieldException, IllegalAccessException {
//prepare
Field mIAmbientLightChangeListeners = getField(fordAmbientLightManager, "mIAmbientLightChangeListeners");
setField(mIAmbientLightChangeListeners, fordAmbientLightManager, null);
//assert
assertDoesNotThrow(() -> {
fordAmbientLightManager.removeIAmbientLightChangeListeners(Mockito.mock(FordAmbientLightManager.IAmbientLightChange.class));
});
}
@Test
public void testRemoveIAmbientLightChangeListeners_mIAmbientLightChangeListenersIsNotNull() throws NoSuchFieldException, IllegalAccessException {
//PREPARE
FordAmbientLightManager.IAmbientLightChange mockListeners = Mockito.mock(FordAmbientLightManager.IAmbientLightChange.class);
List<FordAmbientLightManager.IAmbientLightChange> test_list = new ArrayList<>();
Field mIAmbientLightChangeListeners_Listeners = getField(fordAmbientLightManager, "mIAmbientLightChangeListeners");
test_list.add(mockListeners);
setField(mIAmbientLightChangeListeners_Listeners, fordAmbientLightManager, test_list);
//INVOKE
fordAmbientLightManager.removeIAmbientLightChangeListeners(mockListeners);
//VERIFY
Field mIAmbientLightChangeListeners_test = getField(fordAmbientLightManager, "mIAmbientLightChangeListeners");
List<FordAmbientLightManager.IAmbientLightChange> test = (List<FordAmbientLightManager.IAmbientLightChange>) mIAmbientLightChangeListeners_test.get(fordAmbientLightManager);
//验证不含想移除的监听器
assertFalse(test.contains(mockListeners));
}
//监听器列表为空:验证函数调用不抛异常
@Test
public void testClearIAmbientLightChangeListeners_mIAmbientLightChangeListenersIsNull() throws NoSuchFieldException, IllegalAccessException {
//prepare
Field mIAmbientLightChangeListeners = getField(fordAmbientLightManager, "mIAmbientLightChangeListeners");
setField(mIAmbientLightChangeListeners, fordAmbientLightManager, null);
//assert
assertDoesNotThrow(() -> {
fordAmbientLightManager.clearIAmbientLightChangeListeners();
});
}
@Test
public void testClearIAmbientLightChangeListeners_mIAmbientLightChangeListenersIsNotNull() throws NoSuchFieldException, IllegalAccessException {
//PREPARE
FordAmbientLightManager.IAmbientLightChange mockListeners = Mockito.mock(FordAmbientLightManager.IAmbientLightChange.class);
List<FordAmbientLightManager.IAmbientLightChange> test_list = new ArrayList<>();
Field mIAmbientLightChangeListeners_Listeners = getField(fordAmbientLightManager, "mIAmbientLightChangeListeners");
test_list.add(mockListeners);
setField(mIAmbientLightChangeListeners_Listeners, fordAmbientLightManager, test_list);
//INVOKE
fordAmbientLightManager.clearIAmbientLightChangeListeners();
//VERIFY
Field mIAmbientLightChangeListeners_test = getField(fordAmbientLightManager, "mIAmbientLightChangeListeners");
List<FordAmbientLightManager.IAmbientLightChange> test = (List<FordAmbientLightManager.IAmbientLightChange>) mIAmbientLightChangeListeners_test.get(fordAmbientLightManager);
//验证列表全部清空
assertTrue(test.isEmpty());
}
//获取类的私有属性
public Field getField(Object test_target, String targetFieldName) throws NoSuchFieldException {
Class c = test_target.getClass();
Field field = c.getDeclaredField(targetFieldName);
field.setAccessible(true);
return field;
}
//更改私有变量的值
public void setField(Field field, Object obj, Object value) throws IllegalAccessException {
field.setAccessible(true);
field.set(obj, value);
}
}
存在问题
- 使用了大量反射
技术细节
- 新建我们想要的列表,用反射代替被测试类的私有变量列表,
- 利用断言判断不同行为的结果,断言通过就代表测试通过
小结
- 一开始写add时为了让列表非空写出了这样的代码
@Test
public void testAddIAmbientLightChangeListeners_mIAmbientLightChangeListenersIsNotNull() throws NoSuchFieldException, IllegalAccessException {
//PREPARE
FordAmbientLightManager.IAmbientLightChange mockListeners = Mockito.mock(FordAmbientLightManager.IAmbientLightChange.class);
List<FordAmbientLightManager.IAmbientLightChange> test_list = new ArrayList<>();
Field mIAmbientLightChangeListeners_Listeners = getField(fordAmbientLightManager, "mIAmbientLightChangeListeners");
test_list.add(mockListeners);
setField(mIAmbientLightChangeListeners_Listeners, fordAmbientLightManager, test_list);
//INVOKE
fordAmbientLightManager.addIAmbientLightChangeListeners(mockListeners);
//VERIFY
Field mIAmbientLightChangeListeners_test = getField(fordAmbientLightManager, "mIAmbientLightChangeListeners");
List<FordAmbientLightManager.IAmbientLightChange> test = (List<FordAmbientLightManager.IAmbientLightChange>) mIAmbientLightChangeListeners_test.get(fordAmbientLightManager);
assertTrue(test.contains(mockListeners));
}
很搞笑的错误,其实new一个list得到的list就是非空的,虽然是空列表但是非空,不是null,因为代码写的少所以这个点一直没发现,,,
2.也有比较简单的单元测试方式(看的小组其他成员,写的比我简单)
public void addISuixintingDisplayStateListenerListeners(ISuixintingDisplayStateListener listener) {
if (mISuixintingDisplayStateListeners != null) {
mISuixintingDisplayStateListeners.add(listener);
}
}
//此处只展示非空情况
@Test
public void testAddIWlcmFrwlAnimationStateListeners_mIWlcmFrwlAnimationStateListenersNotNull() throws NoSuchFieldException, IllegalAccessException {
Field field = FordCarVendorExtensionManager.class.getDeclaredField("mIWlcmFrwlAnimationStateListeners");
List<FordCarVendorExtensionManager.IWlcmFrwlAnimationState> mIWlcmFrwlAnimationStateListeners = Mockito.mock(List.class);
FordCarVendorExtensionManager.IWlcmFrwlAnimationState listener = Mockito.mock(FordCarVendorExtensionManager.IWlcmFrwlAnimationState.class);
field.setAccessible(true);
field.set(target, mIWlcmFrwlAnimationStateListeners);
target.addIWlcmFrwlAnimationStateListeners(listener);
//直接验证mock后的list是否调用了add函数
Mockito.verify(mIWlcmFrwlAnimationStateListeners).add(listener);
}