Hatena::ブログ(Diary)

すにぺっと

2011-04-23

clojureプログラミング入門-30 clojureでの単体テスト

| 19:10 | clojureプログラミング入門-30 clojureでの単体テストを含むブックマーク

clojureには単体テストを実施するため、

標準でtest関数clojure.contrib.test-isという

2つの仕組みをもっている。

まずはtest関数から。


:testでのテスト 

これは関数の:testメタデータにテストを付加する方法。

まずはサンプルとなる:testつき関数定義。

add.clj

(defn
  #^{:test (fn []
			 (assert (= 10 (add 7 3)))
			 (assert (= 0 (add 0 0)))
			 )
	 }
  add [ x y ] ( + x y ))

:testメタデータの値は関数とみなされる。

定義した関数について、

(assert expr)

が真になるように。

user=>  (load-file "/path/to/add.clj")
#'user/add
user=> (test add)
:no-test
user=> (test #'add)
:ok

(test a-var)

でテストが実行できる。

:testがあるかどうか調べ、あれば実行する。

a-varはシンボルでなくvar。

:testは単純でコードのすぐそばに書いておけるが、

コードとは別にしたい場合もある(それのほうが多いかも)。

その場合はtest-isを使用する。


test-isを使ったテスト

test-isライブラリを使用すると、

deftestマクロでテストが書ける。

(clojure.contrib.test-is/deftest testname & forms)

テストの本体であるformsにはマクロアサーションを書く。

(clojure.contrib.test-is/is form message?)

formは任意の述語、messageは省略可能なエラーメッセージ


まずexample/add.cljを作成。

(ns example.add)
(defn add-int [ x y ] ( + x y ))

そして、test/testAdd.cljを作成

(ns test.add (:use example.add  clojure.contrib.test-is))

(deftest test-add
  (is (= 3 (add-int 1 2))))

ここに定義されているdeftestマクロが実行される。


REPLで確認する。

user=> (ns test)  ;1
nil
test=>  (use 'clojure.contrib.test-is) ;2
nil
test=>  (load-file "/path/to/example/example/add.clj") ;3
#'example.add/add
test=> (load-file "/path/to/example/test/testAdd.clj") ;4
#'test/test-add
test=> (run-tests) ;5

Testing test

Ran 1 tests containing 1 assertions.
0 failures, 0 errors.
{:type :summary, :test 1, :pass 1, :fail 0, :error 0}

1. nsでname spaceを移動。

もし移動しない場合、

(run-tests 'test.add)

とname spaceを指定すればテストが実行できる。

2.run-testsを実行できるように読込み。

3,4.テスト対象のファイルとテストファイルをロード。

5.テスト実行。


テストの数が少なかったりシンプルであれば

:test。数が増えてきたり複雑なテストをするならtest-isや

その他テストライブラリを使用するべし。

ちなみに、jUnitなどのJavaユニットテストライブラリ

問題なく使える。


プログラミングClojure
プログラミングClojure
posted with amazlet at 11.04.23
Stuart Halloway
オーム社
売り上げランキング: 75886

play-scalaを改めて学ぶ-10 Anormを使用するその1

| 15:57 | play-scalaを改めて学ぶ-10 Anormを使用するその1を含むブックマーク

今回はDBアクセスのための新ライブラリ、Anormについてお勉強。

http://scala.playframework.org/documentation/scala-0.9/anorm


Anorm, SQL data access with Play Scala

Scalaモジュールは、データベースアクセスを行う手段として、

シンプルな結果の解析やデータ変換などを行うAnormと呼ばれる

新しいデータアクセス層が含まれている。


Anorm is Not a Object Relational Mapper

次のドキュメントでは、MySQLのworldサンプルDBを使用する。

サンプルDBをつくるため、MySQLのWebサイトを見て、サンプルDBを作成。

conf/application.confファイルに設定を追加する。

db=mysql:root@world


Overview

Anormは最近のデータベースへのアクセス手法にくらべると、

ちょっと古臭い手法に感じるかもしれない。

特にHibernateなどのORマッパーに慣れているJavaデベロッパーには。

ORMのようなツールは、Java言語ではとても有用だが、

Scalaのようなパワフルな関数型言では逆効果になる。


Using JDBC is a pain, but we provide a better API

直接JDBC APIを使用するのは非常に面倒。

特にJavaの場合、チェック例外をどうするか、ResultSetから行データを取り出して

Javaデータ型にマップしたり、走査したりと何度繰り返したことか。

しかし、例外で頭を悩ます必要はないし、関数型言語Scalaならデータを変換するのも簡単。

実際にplay-scalaでは効果的にScalaオブジェクトJDBCデータの変換を実施する。


You don’t need another DSL to access relational Database

SQLは、RDBにアクセスするための最良のDSL

新しいものを発明する必要はないが、SQL構文および機能はDBベンダにより異なる場合がある。

DSLのような独自のSQLを使用した場合、

ベンダー専用のいくつかの'方言'に対処しなければならないが、

特定のデータベースの独自機能を使用してのことは自分を制限している。

ここの意味がよくわからん。。

A type safe DSL to generate SQL is a mistake

すべてのクエリコンパイラによってチェックされるので

タイプセーフなDSLは優れていると主張しているが、

コンパイラは、DBスキーマへのデータ構造をメタモデルの定義に基づいて

マッピング内容をチェックしている。


そして、このメタモデルが正しい保証はない。

コンパイラは、コードとクエリが正しく入力されていると言っても、

実際に使用するデータベースとの定義不一致が原因で、実行時に失敗することがある。


Take Control of your SQL code

ORマッパーでは、シンプルな例に適している。

しかし既存のデータベースに対応する必要がある場合、

複雑なスキーマや複雑なSQLについてクエリを生成するために

ほとんどの時間を使うことになる。

Anormを使用した場合、単純な'hello world'アプリケーションをつくるのは退屈だが、

実際のアプリケーションでは、SQLコードを完全に制御することによって簡略化し、

最終的には時間を節約できる。

では、play-scalaを使用してSQLデータベースを管理するのを見てみる。


Executing SQL requests

SQLリクエスト方法を学んでみる。

play.db.anorm._をimport。

そしてSQLオブジェクトクエリを作成する。

import play.db.anorm._ 
 
val result:Boolean = SQL("Select 1").execute()

execute()メソッドSQLが成功したかどうかBooleanを返す。

更新時,executeUpdate() を使用する。

その場合、MayErr[IntegrityConstraintViolation,Int]が返ってくる。

val result = SQL("delete from City where id = 99").executeUpdate().fold( 
    e => "Oops, there was an error" , 
    c => c + " rows were updated!"
)

Scalaは複数行文字列サポートしているので、

複雑なSQL文のためにはそれを使うと便利。

var sqlQuery = SQL(
    """
        select * from Country c 
        join CountryLanguage l on l.CountryCode = c.Code 
        where c.code = 'FRA';
    """
)

SQLクエリが動的パラメータを必要とする場合、

クエリ文字列に{name}のようなプレースホルダを宣言し、それをあとで割り当てる。

SQL(
    """
        select * from Country c 
        join CountryLanguage l on l.CountryCode = c.Code 
        where c.code = {countryCode};
    """
).on("countryCode" -> "FRA")

こんな書き方もOK.

SQL(
    """
        select * from Country c 
        join CountryLanguage l on l.CountryCode = c.Code 
        where c.code = {countryCode};
    """
).onParams("FRA")

Retrieving data using the Stream API

selectクエリからのデータにアクセスする最初の方法は、ストリームAPIを使用すること。

// Create an SQL query
val selectCountries = SQL("Select * from Country")
 
// Transform the resulting Stream[Row] as a List[(String,String)]
val countries = selectCountries().map(row => 
    row[String]("code") -> row[String]("name")
).toList

次の例では、データベース内の国の数をカウントする。

そのため、結果セットが単一の列を持つ単一の行になる。

// First retrieve the first row
val firstRow = SQL("Select count(*) as c from Country").apply().head
 
// Next get the content of the 'c' column as Long
val countryCount = firstRow[Long]("c")

次はAnormの続きから。

play-scalaを改めて学ぶ-9 play-scalaでのテスト

| 12:25 | play-scalaを改めて学ぶ-9  play-scalaでのテストを含むブックマーク

今回はplay-scalaをつかったテストの勉強。

http://scala.playframework.org/documentation/scala-0.9/test


Testing your application

play-scalaは様々なスタイルのテストを提供する、ScalaTestが付属している。

Javaの場合と同様、testディレクトリscalaのソースを置く必要がある。

また、テストランナーを有効にするにはplay testを使用してアプリケーションを実行する。

scala testの詳細情報はこちらで。


JUnit Style

これはテストメソッドに @Testアノテーションを使用する。

clsass JUnitStyle extends UnitTestCase with AssertionsForJUnit {
    
    @Before def setUp = Fixtures.deleteAll()
    
    @Test def verifyEasy {
        assert("A" == "A")
        intercept[StringIndexOutOfBoundsException] {
            "concise".charAt(-1)
        }
    }
    
}

JUnit Style with Should matchers

ShouldMatchersトレイトがテストアサーション用のドメイン特化言語DSL)を提供している。

class JUnitStyleWithShould extends UnitTestCase with ShouldMatchersForJUnit {
    
    @Before def setUp = Fixtures.deleteAll()
    
    @Test def verifyEasy {        
        val name = "Guillaume"        
        name should be ("Guillaume")       
        evaluating { 
            "name".charAt(-1) 
        } should produce [StringIndexOutOfBoundsException]       
        name should have length (9)       
        name should include ("i")       
        name.length should not be < (8)       
        name should not startWith ("Hello")
    }
    
}

Functional suite Style

ここでは、test毎に関数が作成される。

class FunctionsSuiteStyle extends UnitFunSuite with ShouldMatchers {
    
    Fixtures.deleteAll()
    
    test("Hello...") (pending)
    
    test("1 + 1") {        
        (1 + 1) should be (2)        
    }
    
    test("Something") {
        "Guillaume" should not include ("X")
    }
    
    test("1 + 1 again") {        
        (1 + 1) should be (2)   
    }
    
}

Specification Style

このスタイルは、BDDスタイルを提供。

class SpecStyle extends UnitFlatSpec with ShouldMatchers {
 
    val name = "Hello World"
 
    "'Hello World'" should "not contain the X letter" in {
        name should not include ("X")
    }
 
    it should "have 11 chars" in {
        name should have length (11)      
    }
    
}

Features list Style

この一連のテストでは、各テストで機能の1つのシナリオを表している。

class FeatureStyle extends UnitFeatureSpec { 
 
    feature("The user can pop an element off the top of the stack") { 
        scenario("pop is invoked on a non-empty stack") (pending)
        scenario("pop is invoked on an empty stack") (pending)
    }
  
}

playではいろいろなスタイルで単体テストが可能。

使いやすいスタイルを選択して使うべし。

次はAnormの巻。