Ohara 在開發時很要求寫測試程式,主要的目的是因為 Ohara 是 Open Source 所以開發者的人數很多,所以不可能一個人就了解所有模組每一段程式的邏輯。因此就有可能不小心改到一段程式碼而影響到其它地方不能執行,最簡單的例子是修改了 Configurator Restful API 傳入參數的資料格式,如果前端沒有撰寫測試程式就會等到使用者在操作 UI 時才會發現程式無法運作,這時前端的開發者就會開始追蹤程式碼,等追到最後才發現 API 傳入的參數有修改。 這個問題如果一開始有撰寫測試可以很快的發現問題並解決,可以減少很多成本的花費,以下列出幾個寫測試程式的好處:
減少一些低級的失誤,例如是程式邏輯寫錯,可以很快的發現問題
程式在重構的時侯,修改起來比較有信心。如果沒有測試程式有很多地方都不敢改
如果發現程式有 bug 可以再增加測試情境,調整程式的邏輯
程式換其它人開發時,如果不了解程式的邏輯也可以先閱讀測試程式,並且可以使用測試程式在 IDE 上執行 Debug mode 來追蹤程式碼
以上就是撰寫測試程式可以得到的幾個好處,雖然寫測試程式有可能會影響到開發的進度,但是這是值得的投資。沒有寫測試程式當系統愈做愈大時所花費的成本會更多,而且程式裡面可能會隱藏許多沒被發現的 Bug,到最後程式可能就不太能修改,加一個功能可能會花費很長的時間。
以下就來簡單的介紹有關於 Ohara 測試程式的部份,Ohara 的測試分為單元測試以及整合測試。今天就來說明單元測試的部份:
單元測試主要用來測試程式的邏輯是否正確,以下舉一段 Ohara 單元測試程式的 sample:
@Test
public void testTags() {
Row row = Row.of(Arrays.asList("tag", "tag2"), Cell.of("aa", "aa"), Cell.of("b", 123));
Assert.assertEquals(2, row.tags().size());
Assert.assertEquals("tag", row.tags().get(0));
Assert.assertEquals("tag2", row.tags().get(1));
}
以上的測試程式主要用來測試 Row 的 tag list 的數量是否正確以及每一個元素內的值是否正確,單元測試主要測試的就是 output 出來的值是否有符合預期的結果,如果沒有符合預期的結果就代表程式邏輯要再去做確認和修改。
在寫測試程式時呼叫方法,傳入的參數如果要求是一個介面 (interface) 或是類別 (class),這時介面的實作或是類別有可能是其它開發者在做,有可能還沒做好或是要避免測試程式的耦合性,不用太關心實作的內容,這時可以使用 mock 的 library 或是建立 Fake 的 class,舉例如下:
Ohara 開發使用的 mock library 是 mockito,版本為 1.10.19,以下是 Ohara 測試程式使用 mock 的一段 sample
@Test(expected = IllegalArgumentException.class)
public void emptyConfigInfos() {
ConfigInfos infos = Mockito.mock(ConfigInfos.class);
Mockito.when(infos.values()).thenReturn(Collections.emptyList());
SettingInfo.of(infos);
}
在這裡主要會去 mock ConfigInfos 類別,並且當呼叫到 infos 的 values 方法時,要求回傳值為空的 List,之後再把 infos 的變數傳給 SettingInfo.of 測試。測試程式預期當 SettingInfo.of 收到空的 list 時會收到 IllegalArgumentException 的 Exception
有關於 SettingInfo 的程式可以參考以下的連結:
https://github.com/oharastream/ohara/blob/master/ohara-kafka/src/main/java/com/island/ohara/kafka/connector/json/SettingInfo.java
以下是建立 Fake class 測試的 sample code:
@Test
def testBkCreatorZKNotExists(): Unit = {
val node1Name = "node1"
val node1 = node(node1Name)
val brokerCollie = new FakeBrokerCollie(Seq(node1), Seq.empty, Seq.empty)
val bkCreator: Future[BrokerClusterInfo] = brokerCollie.creator
.clusterName("cluster123")
.imageName(BrokerApi.IMAGE_NAME_DEFAULT)
.zookeeperClusterName("zk123456")
.clientPort(9092)
.exporterPort(9093)
.jmxPort(9094)
.nodeName(node1Name)
.create()
an[NoSuchClusterException] should be thrownBy {
Await.result(bkCreator, TIMEOUT)
}
}
以上的測試程式主要用來測試在建立 Broker cluster 時,如果沒有建立 zk123456 的 Zookeeper Cluster 會收到 NoSuchClusterException,在這裡 FakeBrokerCollie 會繼承 BrokerCollie,creator 的程式邏輯會寫到 BrokerCollie 的 trait ,這樣就可以使用 FakeBrokerCollie 去測試 creator 方法的邏輯。不用等到 K8SBrokerCollieImpl 或是 ssh 版本的 BrokerCollieImpl 實作完成,再去做寫測試的部份。 FakeBrokerCollie 的好處就是不用 Kubernetes 和 SSH 的環境也可以執行程式的邏輯測試。
有了 mock 以及建立 Fake class 的測試還是不夠的,因為在 Connector 的執行,是多執行緒在執行,所以有可能我們使用 mock 或是 Fake 的測試邏輯沒問題,但是在部署到真實的 Worker cluster 環境上執行確發現資料筆數,跟我們預期的不一樣。 因此 Ohara 提供在測試程式裡面執行 MiniCluster 的測試方式,確保 connector 程式的執行結果是正確的,以下是建立 MiniCluster 程式簡單的寫法範例:
val brokerNumber = 1
val workerNumber = 1
val zookeeper = Zookeepers.local(0)
val brokers = Brokers.local(zookeeper, (1 to brokerNumber).map(x => 0).toArray)
val workers = Workers.local(brokers, (1 to workerNumber).map(x => 0).toArray)
val workerClient = WorkerClient(workers.connectionProps())
以上的寫法可以設定 Zookeeper、Broker 和 Worker cluster 的數量,如果數量設定太大,硬體 (CPU、記憶體) 不夠強的話有可能測試程式會發生 timeout exception。
啟動了 cluster 之後就可以把 worker 的連線資訊設定給 WorkerClient,這樣就可以在測試程式上執行 Connector 的測試了。
完整的 connector 測試寫法可以參考 Day 20 和 Day 23
今天已經介紹了有關於 Ohara 單元測試的部份了,明天會再繼續說明有關於整合測試的部份。