UnitTest // RUnit(R ユニットテストフレームワーク)パッケージ中のオブジェクト一覧
UnitTest は関数をテストするコードを記述することで,関数の正しい振舞いを保証します.
その結果,随時改変が行なわれてもバグが存在しない(であろう)ことが期待されます.
RUnit は R での UnitTest 実行のためのフレームワークです.
RUnit の使い方を簡単にまとめてみます.
これから定義する関数の満たすべき仕様を考えます.
和を求める mysum() を作成することにします.実際には sum() をラッピングするだけです.
mysum() がクリアするべきテストのひとつとして,
mysum(1:10) == 55
がすぐに思い浮かびます.
ワーキングディレクトリに tests.R ファイルを作成します.
tests.R に test.mysum() を記述.慣習として,test.テストする関数名 とします.
これは,runTestFile() の引数 testFuncRegexp で解釈されますので,
testFuncRegexp に適当な正規表現で与えれば変更できます.
### tests.R test.mysum <- function() { checkEqualsNumeric(mysum(1:10), 55) }
そして,おもむろに runTestFile() します.
> runTestFile("tests.R") Error in data.class(target) : couldn't find function "mysum" Timing stopped at: 0 0 0 0 0 Number of test functions: 1 Number of errors: 1 Number of failures: 0
テストは失敗します.まだ mysum() を定義していませんので当然です.
ここで大切なのは,「テストは確実に行なわれている」ということです.
mysum() を定義します.
### tests.R test.mysum <- function() { checkEqualsNumeric(mysum(1:10), 55) } mysum <- function(x) { }
再びテストを実行します.
> runTestFile("tests.R") Error in checkEqualsNumeric(mysum(1:10), 55) : Mean relative difference: 10 Timing stopped at: 0 0 0 0 0 Number of test functions: 1 Number of errors: 0 Number of failures: 1
今回も失敗しますが,エラーメッセージは異なります.これは mysum() が期待される値を返していない,つまり,バグを持っているということです.したがって,このバグをなくさなくてはいけません.
といっても,この場合 mysum() がなにもしていない(ので,返値は NULL)だけですが.
mysum() のバグを潰す.具体的には sum() の値を返すことにします.
### tests.R test.mysum <- function() { checkEqualsNumeric(mysum(1:10), 55) } mysum <- function(x) { sum(x }
そしてテスト.
> runTestFile("tests.R") Error in parse(file, n, text, prompt) : syntax error on line 7 Number of test functions: 1 Number of errors: 1 Number of failures: 0
あれ? エラーになってしまいました.
Number of erros: には関数にエラーがあるということを示しています.一方,Number of filures: は,testEqualsNumeric() などでの評価が失敗していることを意味します.つまり,前者は R のスクリプトとして間違っている,後者はプログラムの挙動として間違っていることを示します.
で,今回はそもそもスクリプトが間違っている(sum() の括弧が閉じていない)ので,これを修正します.
### tests.R test.mysum <- function() { checkEqualsNumeric(mysum(1:10), 55) } mysum <- function(x) { sum(x) }
そしてテストする.
> runTestFile("tests.R") Number of test functions: 1 Number of errors: 0 Number of failures: 0
やたっ♪ 成功しました.
test.my.oneway.ANOVA <- function() { # equal=TRUE のときのチェック result <- my.oneway.ANOVA(c(8, 11, 22, 6), c(135.83, 160.49, 178.35, 188.06), c(19.59, 12.28, 15.01, 9.81)^2) checkEqualsNumeric(c(13669.396, 3, 4556.4655, 20.82824, 1.737484e-8, 9406.843, 43, 218.7638, 99999, 99999, 23076.240, 46, 501.6574, 99999, 99999), as.numeric(t(result)), tolerance=0.000001) # equal=FALSE のときのチェック result <- my.oneway.ANOVA(c(8, 11, 22, 6), c(135.83, 160.49, 178.35, 188.06), c(19.59, 12.28, 15.01, 9.81)^2, equal=FALSE) checkEqualsNumeric(c(result$F, result$df1, result$df2, result$P) , c(17.56461,3,16.50403,2.191921e-05), tolerance=0.000001) } my.oneway.ANOVA <- function(n, m, u, equal=T) { # stopifnot(length(n) == length(m), length(m) == length(u), n > 1, u > 0, floor(n) == n) ng <- length(n) if (equal) { # 分散が等しいと仮定する場合 nc <- sum(n) sw <- sum(u*(n-1)) sb <- sum(n*(m-sum(n*m)/nc)^2) ss <- c(sb, sw, sb+sw) df <- c(ng-1, nc-ng, nc-1) ms <- ss/df f <- p <- rep(99999, 3) f[1] <- ms[1]/ms[2] p[1] <- pf(f[1], df[1], df[2], lower = F) anova.table <- cbind(ss, df, ms, f, p) colnames(anova.table) <- c("SS", "d.f.", "MS", "F value", "P value") rownames(anova.table) <- c("between class", "within class", "total") anova.table } else { # 分散が等しいと仮定しない場合 w <- n/u m0 <- sum(w*m)/(sum.w <- sum(w)) temp <- sum((1-w/sum.w)^2/(n-1))/(ng^2-1) f <- sum(w*(m-m0)^2)/((ng-1)*(1+2*(ng-2)*temp)) p <- pf(f, ng-1, 1/(3*temp), lower=F) list(F=f, df1=ng-1, df2=1/(3*temp), P=p) } }まだテストしなくてはならない項目があるが,一応。 -- 2004-07-06 (火) 19:23:42