1. 单元测试简介
单元测试就是针对最小的功能单元编写测试代码。Java程序最小的功能单元是方法,因此,对Java程序进行单元测试就是针对单个Java方法的测试。
1.1. JUnit
JUnit 是一套测试框架,JUnit是一个开源的Java语言的单元测试框架,专门针对Java设计。
最近版本是 Junit 5。
2. 如何使用 JUnit 编写单元测试
2.1. 项目目录结构
2.2. 自动生成测试类
IntelliJ IDE 里面已经集成了 Junit 库,可以直接引用,方法如下:
1.在要生成测试类的类里面,按ctrl+shift+t –> create new test
这里选择的是 JUnit4 的库,4 和 5 的规则稍有不同,下面的讲解都以JUnit4为主
2.3. 编写测试代码
2.3.1. TestCase 之前的准备工作
JUnit 4 很多规则都是使用注解来完成,比如:
- @Before:会在每一个测试方法被运行前执行一次;一般会做初始化的工作
- @After:会在每一个测试方法运行后被执行一次;做一些清除工作,避免影响到下一条测试用例
public class SensorsTest {
public SensorsTest() throws IOException, InvalidArgumentException {
}
…
//execute for each test, before executing test
@Before
public void before() throws NoSuchFieldException, IllegalAccessException {
System.out.println("in before");
final ISensorsABTest abTest = new SensorsABTest(abGlobalConfig);
ExperimentCacheManager cacheManager =
getExperimentCacheManagerByReflect(getSensorsABTestWorkerByReflect(abTest));
getExperimentResultCacheByReflect(cacheManager).invalidateAll(); //强制清除实验缓存
EventCacheManager eventCacheManager =
getEventCacheManagerByReflect(getSensorsABTestWorkerByReflect(abTest));
getEventCacheByReflect(eventCacheManager).invalidateAll(); //强制清除事件缓存
}
//execute for each test, after executing test
@After
public void after() {
System.out.println("in after");
}
相似的注解还有
@BeforeClass: 会在所有的方法执行前被执行,static 方法 (全局只会执行一次,而且是第一个运行)
@AfterClass:会在所有的方法执行之后进行执行,static 方法 (全局只会执行一次,而且是最后一个运行)
2.3.2. TestCase 编写
使用注解 @Test 修饰一个TestCase,
测试方法必须使用 public void 进行修饰,不能带参数
@Test(timeout=毫秒数),表示方法的超时时间,如果超时会报错
/**
* 立即调用,返回试验JSON类型,SDK JSON类型为JSON 字符串类型。
*/
@Test
public void asyncFetchABTestReturnRightResult() throws IOException, InvalidArgumentException {
//初始化 AB Testing SDK
final ISensorsABTest abTest = new SensorsABTest(abGlobalConfig);
// Experiment<String> result = abTest.asyncFetchABTest(distinctId, false, experimentName, "{\"color\":\"grey\"}");
Experiment<Integer> result = abTest.asyncFetchABTest(distinctId, false, experimentName, -1);
System.out.println(" ========== " + result.getResult());
assertNotNull(result);
assertNotNull(result.getDistinctId());
assertNotNull(result.getIsLoginId());
assertNotNull(result.getResult());
assertTrue(result.getResult() != null);
}
其中,我们使用断言 assertXXX 方法来判断执行结果是否正确,断言法法如下:
编号 断言
1 void assertEquals(boolean expected, boolean actual)
检查两个变量或者等式是否平衡
2 void assertTrue(boolean condition)
检查条件为真
3 void assertFalse(boolean condition)
检查条件为假
4 void assertNotNull(Object object)
检查对象不为空
5 void assertNull(Object object)
检查对象为空
6 void assertSame(Object expected, Object actual)
assertSame() 方法检查两个相关对象是否指向同一个对象
7 void assertNotSame(Object expected, Object actual)
assertNotSame() 方法检查两个相关对象是否不指向同一个对象
8 void assertArrayEquals(expectedArray, resultArray)
assertArrayEquals() 方法检查两个数组是否相等
2.4. 测试执行
在 IntelliJ 中可以直接测试执行单个的 TestCase,如下:
或者执行所有的测试方法:
测试结果如下:
3. Java A/B Testing 测试 Tips
3.1. 接口超时测试方法
SDK-3100 - 【Java A/B Testing SDK 自定义属性】timeout_设置为-1或者0,应该取默认值 3s 完成
操作步骤:
1.调用 AsyncFetchABTest 接口
2.传入必填参数
3.设置弱网(Charles 对 测试Demo的请求设置断点 )
4.超时参数输入0
5.
// Java 测试代码
ABGlobalConfig abGlobalConfig = ABGlobalConfig.builder().setSensorsAnalytics(sa).setApiUrl("http://localhost:8887/timeoutTest").build();
ISensorsABTest sensorsABTest = new SensorsABTest(abGlobalConfig);
int timeout = 0
Map<String, Object> customProperties = Maps.newHashMap();
customProperties.put("age", 15);
Long start = System.currentTimeMillis();
Experiment<String> experiment =
sensorsABTest.asyncFetchABTest("123", true, "fz",
"eee", timeout, customProperties);
Long end = System.currentTimeMillis();
log.info(getTimeStamp(end-start) + "实验结果" + experiment.getResult());
assertNotNull(experiment);
assertNotNull(experiment.getAbTestExperimentId());
assertEquals(3000, end-start, 100);
3.1.1. 注意:直接配置 IntellJ 的代理 依然是无法用 Charles 抓到请求包,所以需要把 ab的请求地址定向到本地 “http://localhost:8887/timeoutTest”
3.2. 自动验证事件中的字段
方法一:通过反射机制拿到 BatchConsumer 中的 messageList 值
@Before
public void init() throws NoSuchFieldException, IllegalAccessException {
String url = "http://10.120.73.51:8106/sa?project=default&token=";
// 注意要设置 bulkSize 稍微大一点,这里设置为 100,否则超过 1 条就上报,messageList 里面拿不到事件数据
batchConsumer = new BatchConsumer(url, 100, true, 3);
// 通过反射机制获取 BatchConsumer 的 messageList
Field field = batchConsumer.getClass().getDeclaredField("messageList");
field.setAccessible(true);
messageList = (List<Map<String, Object>>) field.get(batchConsumer); // messageList 是 BatchConsumer 用来暂存事件数据的成员变量
saTmp = new SensorsAnalytics(batchConsumer);
}
private void assertNotNullProp(){
assertNotNull(messageList.get(0).get("identities"));
assertNotNull(messageList.get(0).get("time"));
assertNotNull(messageList.get(0).get("_track_id"));
assertNotNull(messageList.get(0).get("properties"));
assertNotNull(messageList.get(0).get("project"));
assertNotNull(messageList.get(0).get("token"));
}
Map<String, Object> props = (Map<String, Object>)messageList.get(0).get("properties");
Boolean isLoginID = (Boolean)props.get("$is_login_id");
assertTrue(isLoginID);
方法二:通过反射机制拿到 ConcurrentLoggingConsumer 中的私有成员变量 messageBuffer 值
consumer = new ConcurrentLoggingConsumer("file.log");
sa = new SensorsAnalytics(consumer);
Field field = consumer.getClass().getSuperclass().getDeclaredField("messageBuffer");
field.setAccessible(true);
messageBuffer = (StringBuilder) field.get(consumer);
assertNotNull(messageBuffer);
assertNotEquals(0, messageBuffer.length());
JsonNode eventJsonNode = SensorsAnalyticsUtil.getJsonObjectMapper().readValue(messageBuffer.toString(), JsonNode.class);
// 检查事件的属性值
assertEquals("$ABTestTrigger", eventJsonNode.findValue("event").asText());
assertEquals("a123", eventJsonNode.findValue("distinct_id").asText());
assertEquals(2, eventJsonNode.get("properties").findValue("$abtest_experiment_id").asInt());
assertEquals(1, eventJsonNode.get("properties").findValue("$abtest_experiment_group_id").asInt());
【SensorsAnalyticsUtil.java】
public static ObjectMapper getJsonObjectMapper() {
ObjectMapper jsonObjectMapper = new ObjectMapper();
jsonObjectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
jsonObjectMapper.setPropertyNamingStrategy(PropertyNamingStrategy.CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES);
jsonObjectMapper.setTimeZone(TimeZone.getDefault());
jsonObjectMapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"));
return jsonObjectMapper;
}