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