關(guān)于單元測試的內(nèi)容很多,關(guān)于可維護(hù)性的內(nèi)容也很多,在這里主要關(guān)注單元測試代碼可維護(hù)性中的兩點。
重復(fù)
條件測試
重復(fù)
重復(fù):同一概念,多處表達(dá)。從形式可以把重復(fù)分類為:代碼內(nèi)容完全一樣,代碼結(jié)構(gòu)相同,結(jié)構(gòu)不同但語義相近。
獲取重復(fù)的感性認(rèn)識
代碼完全相同
在多個文件中到處充斥著
log("當(dāng)前網(wǎng)絡(luò)不可達(dá)!");
在比如, 下面的兩處空字符串""完全一樣,兩處 "plainText"完全一樣.
public class TemplateTest {
@Test
public void emptyTemplate() throws Exception {
assertEquals("", new Template("").toString());
}
@Test
public void plainTextTemplate() throws Exception {
assertEquals("plainText", new Template("plainText").toString());
}
}
代碼結(jié)構(gòu)相同
比如下面的代碼,結(jié)構(gòu)是相同的
public class TemplateTest {
@Test
public void emptyTemplate() throws Exception {
String template = "";
assertEquals(template, new Template(template).toString());
}
@Test
public void plainTextTemplate() throws Exception {
String template = "plainText",;
assertEquals(template, new Template(template).toString());
}
}
<a name="semanteme_repeat"></a>結(jié)構(gòu)不同但語義相近
下面兩處內(nèi)容、結(jié)構(gòu)都不同相同。但可以抽象為通過某種條件對一個集合進(jìn)行過濾,最后得到目標(biāo)集合。
@Test
public void groupShouldContainTwoSupervisors() {
List<Employee> all = group.list();
List<Employee> all = new ArrayList<>(all);
Iterator<Employee> i = employees.iterator();
while(i.hasNext()) {
Employee employee = i.next();
if (!employee.isSupervisor()) {
i.remove();
}
}
assertEquals(2, employee.size());
}
@Test
public void groupShouldContainFiveNewcomers() {
List<Employee> newcomers = new ArrayList<>();
for (Employee employee : group.list()) {
DateTime oneYearAgo = DateTime.now().minusYears(1);
if (employee.startingDate().isAfter(oneYearAgo)) {
newcomers.add(employee);
}
}
assertEquals(5, newcomers.size());
}
去重后
@Test
public void groupShouldContainTwoSupervisors() {
groupShouldContains(2, group.list(), employeeFilter);
}
@Test
public void groupShouldContainFiveNewcomers() {
groupShouldContains(2, group.list, customFilter);
}
抽象接口
public interface Filter<T> {
List<T> filter(List<T> objects);
}
抽取公共方法
private void groupShouldContains(int expectedCount, List group, Filter filter) {
List list = filter.filter(group);
assertEquals(expectedCount, list.size());
}
怎么去掉重復(fù)
根據(jù)重復(fù)的概念---“同一個概念,在多處表達(dá)” 所以我們要第一步先抽象出表達(dá)的概念。第二步 把所有出現(xiàn)重復(fù)的地方用我們抽象的概念形成一個統(tǒng)一的命題。
第一步 抽象概念
我要打印一個樹,可以這樣實現(xiàn),printTree 等價于11條print語句
print " * "
print " *** "
print " ***** "
print "*******"
print " * "
print " *** "
print " ***** "
print "*******"
print " # "
print " # "
print " # "
同樣的問題還可以這樣看:printTree等價于先打印兩個樹冠,在打印一個樹干。
printCrown();
printCrown();
printTreeTrunk();
這里的樹冠(crown)和樹干(trunk)就是我們抽象出來的概念。
在上面語義重復(fù)的例子 抽象出的概念是過濾器的概念。
第二步 統(tǒng)一命題
在上面語義重復(fù)的例子 抽象出過濾器的概念后,可以形成這樣一個統(tǒng)一的命題:如果一個原始集合經(jīng)過過濾器過濾,那么會生成一個符合預(yù)期的新集合。
有了過濾器的概念不難定義一個過濾器對象,然后把命題用過濾器對象翻譯出來。
從可讀性的角度看重復(fù)
我們知道可讀性是可維護(hù)性的前提,那么重復(fù)的代碼是怎么影響可讀性的呢?
首先要說一下可讀性的概念。什么是可讀性?可讀性可以定義為理解一段代碼的意圖所需要的時間。時間越短可讀性越好。
而重復(fù)會造成我們看到同一個概念還要繼續(xù)思考一下才明白這是同一個概念。如果重復(fù)的形式是內(nèi)容完全相同那么還好一下,如果是結(jié)構(gòu)重復(fù),甚至是語義重復(fù),那么我們理解代碼段的意圖耗費的時間就更長了,如下圖所示。
所以不管是在產(chǎn)品代碼還是測試代碼中我們都要盡最大努力去掉重復(fù)。
條件測試
待續(xù)...