1. 程式人生 > >[轉] Akka 使用系列之二: 測試

[轉] Akka 使用系列之二: 測試

[From] http://www.algorithmdog.com/akka-test

 

通過上一篇文章,我們已經大致瞭解怎麼使用 Akka,期待細緻用法。這篇文章將介紹如何用 Akka-testkit 對 Akka 程式進行測試。

並行程式是最難除錯的程式型別之一,因此做好測試是相當重要的事情。為了減輕 Akka 的程式的測試難度, Akka 官方專門開發了一個測試工具包 Akka-testkit。

 

1 Actor 的測試需求

      對於一個 Actor, 我們要測什麼呢?不同的文章有不同的說法,比如 

http://rerun.me/2014/09/29/akka-notes-logging-and-testing/ 就把 Actor 測試需求分為:1)傳送訊息給 Actors, 2)測試內部狀態,3)測試日誌和 4)帶引數 Actor 的測試。我個人認為,對於一個 Actor, 我們要測的有三個方面:1)Actor 接收訊息之後,是否返回正確的訊息,2)Actor 接收訊息之後,是否正確地改變內部狀態和執行內部行為,3)Actor 接收訊息之後,是否正確地發訊息給後續 Actor。當然這是我的一家之言,有什麼不完善的地方,歡迎大家討論。下面是一個簡單的示例圖。

akka-testkit

下面是 studentActor 的一段程式碼,反應了 studentActor 接受到早上時間訊息之後的動作,包括:1)給環境或者鬧鐘迴應“關閉鬧鐘”,2)內部變數 DayInSchool 加 1,3)向老師傳送問題訊息。這段程式碼將包含所有要測試的元素,後面我們將示例怎麼用 Akka-testkit 測試這段程式碼。

  def receive = {
      case time:Long => {

        val originalSender = sender;
        sender ! "關閉鬧鐘" 

        DayInSchool += 1;
        log.info("DayInSchool is %d".format(DayInSchool))

        remoteTeacherRef ! "歷史上規模最大的眾籌行動是什麼?";
      }
    }

 

2 不適用的 Scalatest

       Scalatest 是 Scala 開發者們最常見的測試工具,其用法非常簡便。下面是一個 Scalatest 的簡單示例。

@RunWith(classOf[JUnitRunner])
class TeacherServiceTest1 extends FunSuite with BeforeAndAfter{
  test("countAnswer"){
     assert(1==1)
  }
}

 

但是我們無法使用 scalatest 測試 Actor。原因在於:1)Scalatest 無法捕捉被測 Actor 迴應的訊息,因此無法測試被測 Actor 是否正確迴應訊息; 2)Scalatest 無法獲取被測 Actor 的內部狀態,因此無法測試被測 Actor 內部狀態的改變是否正確; 3) Scalatest 無法捕捉被測 Actor 對外發送的訊息,因此無法測試被測 Actor 對外發送的訊息是否正確。因此有必要針對 Akka 開發一套測試工具, Akka-testkit 測試包應運而生。

 

3 Akka-testkit 的使用

      Maven 專案要使用 Akka-testkit,需要在 pom.xml 檔案中加入 akka-testkit 包,如下所示。

<dependency>
    <groupId>com.typesafe.akka</groupId>
    <artifactId>akka-testkit_2.10</artifactId>
    <version>2.2.5</version>
</dependency>

 

然後編寫單元測試的程式碼,其基本範例如下。

@RunWith(classOf[JUnitRunner]) //
class StudentActorTest
  extends TestKit(ActorSystem("SummerSchool",
    ConfigFactory.parseString("""一些配置""")))
  with WordSpecLike
  with BeforeAndAfterAll{
   
     "A actor " must {
       "acts in this way" in {
          測試程式碼寫在這裡。
        }
     }

  }

 

      Akka-testkit 的主要工具包括, 1) testProbe 用於測試 Actor 迴應和傳送訊息,testActor 用於簡便情況下測試 Actor 迴應訊息,和 2) testActorRef 用於測試 Actor 內部狀態的改變。

 

3.1 迴應訊息的測試

      對於被測 Actor 是否正確地迴應訊息,可以用 testProbe 測試。首先將 testProbe 給被測 Actor 傳送訊息,再看 testProbe 是否接受到期望的迴應訊息。下面是一個示例。

 //test its responses
  "The countAnswer " must {
    "response a correct answer order" in {
      val studentActor 
          = system.actorOf(Props(new StudentActor(teacherActor )))
      val testProb   = new TestProbe(system);
      testProb.send(studentActor, 7.toLong);
      //testProbe 給 studentActor 傳送 “早上 7 點啦” 的訊息
      testProb.send(studentActor, 7.toLong)

      testProb.expectMsg("關閉鬧鐘")
      //測試 testProbe 是否收到預期迴應訊息
      testProb.expectMsg("關閉鬧鐘")
    }
  }

 

      除了使用 testProbe 之外,Akka-testkit 還提供一種簡便方法: 使用 testActor。 如果測試類實現特質 ImplicitSender, studentActorRef ! 7.toLong 傳送給 studentActor 的訊息 7.toLong 就是從 testActor 來的。然後在呼叫 expectMsg(“關閉鬧鐘”) 就可以測試 testActor 是否收到 studentActor 迴應訊息 “關閉鬧鐘” 了。具體程式碼如下所示。

class StudentActorTest
  extends TestKit(ActorSystem("SummerSchool",
    ConfigFactory.parseString("""一些配置""")))
  with ImplicitSender //加這句,把testActor 設定為訊息發出 Actor
  with WordSpecLike
  with BeforeAndAfterAll{

  "StudentActor" must {
    "response correctly" in {
      val studentActorRef 
      = system.actorOf(Props(new StudentActor(teacherActor )))
      studentActorRef ! 7.toLong; 
      //testActor 發出 7.toLong 訊息給 studentActor
      expectMsg("關閉鬧鐘") 
      //testActor 應該收到 studentActor 迴應訊息 "關閉鬧鐘"
    }
  }

}

 

我們可以看出,使用 testActor 的程式碼比使用 testProbe 的簡便。但是,一個東西的用法越是簡便,功能便越缺失。testActor 最大的缺失是隻能接受被測 Actor 發來的一個迴應訊息。比如下面的程式碼就會報錯。

"StudentActor" must {
    "response correctly" in {
      val studentActorRef 
      = system.actorOf(Props(new StudentActor(teacherActor )))
      studentActorRef ! 7.toLong; 
      studentActorRef ! 8.toLong; 
      expectMsg("關閉鬧鐘") 
      expectMsg("關閉鬧鐘") 
    }

 

3.2 內部狀態的測試

      對於被測 Actor 內部狀態的改變,可以用 TestActorRef 進行測試。TestActorRef.underlyingActor 可以探測被測 Actor 的內部,用於測試被測 Actor 內部狀態是否符合預期。 下面是一個示例。

"StudentActor" must {
    "increase the DayInSchool" in {
      val testActorRef 
       = TestActorRef(new StudentActor(teacherActor)) 
      // 建立 Actor 的TestActorRef
      testActorRef ! 7.toLong; 
      assert(testActorRef.underlyingActor.DayInSchool == 1); 
      // TestActorRef.underlyingActor 探測 DayInSchool 變數是否符合預期。
    }
}

 

3.3 發出訊息的測試

      對於被測 Actor 是否正確地發出訊息,也可以用 testProbe 測試。首先將 testProbe 設定為被測 Actor 發出訊息的目標,然後讓被測 Actor 發出訊息,再看 testProbe 是否接受到期望的訊息。下面是一個示例。

"StudentActor " must{
    val questionReceiver 
      = TestProbe()
    val studentActorRef 
      = system.actorOf(Props(new StudentActor(questionReceiver.ref))) 
    // 設定為 studentActor 傳送訊息的目標

    "send a question after waking up" in {
        studentActorRef ! 7.toLong 
        studentActorRef ! 7.toLong
        /*給 studentActor 傳送訊息“7點啦” ,
         *studentActor 會給老師(這裡的 questionReceiver.ref) 傳送問題
         */

        questionReceiver.expectMsg("歷史上規模最大的眾籌行動是什麼?")
        questionReceiver.expectMsg("歷史上規模最大的眾籌行動是什麼?")
        //模擬老師的 testProbe 是否收到預期問題
    }
}

 

4 總結

       Akka-testkit 是 Akka 官方推出的 Akka 測試工具包,用於減輕 Akka 程式的測試難度。Akka-testkit 的主要工具包括, 1) testProbe 用於測試被測 Actor 迴應和傳送訊息,testActor 用於簡便情況下測試被測 Actor 迴應訊息,和 2) testActorRef 用於測試被測 Actor 內部狀態的改變。完整的專案程式碼已經上傳到 Github 上了。被測 Actor 是 org.algorithmdog.akkalearning.StudentActor, 測試類是 org.algorithmdog.akkalearning.StudentActorTest。

      這篇文章難產了很長一段時間,對不住支援我的讀者們。對不起。Akka 和 Actor 模型對我來說是一個全新的東西,花了比較多的時間學習和熟悉。學習之後,覺得第一篇寫得太不清楚了,準備重構第一篇。對於這篇文章質量,我個人比較滿意的,甚至敢認為這篇文章應該是國內關於 Akka-testkit 最清楚的文章之一(ps:大牛們輕噴)。最後歡迎關注我的公眾號,每兩週的更新就會有提醒哦~