Javaでprivateメソッドをmockに置き換える方法

探していたら、mockitoでは実装してない旨のページを見つけた。

github.com

powermockでできるからそっち使ってねって書いてある(ように読める)…○r2”

社内のMさんとOさんに助けを求めたら教えてもらえたのだが、 どうやら、jmockitなるものを使えば、簡単にstaticだろうがfinalだろうがprivateだろうがmockに置き換えられる模様。 ↓教えてもらったサイト↓ qiita.com

やり方を具体例で教えてもらったが、確かに簡単かつ意図したとおりにできた!

ので、今後お世話になりそうなのでメモしておく。

以下、サンプルコード

package test.jmockit;

public class JmockitSample {
    public static void main(String[] args){
        System.out.println(new JmockitSample().methodPublic());
        System.out.println(methodPublicStatic());
        System.out.println(new JmockitSample().callPraivateMethod());
        System.out.println(callPrivateStaticMethod());
    }

    public String methodPublic() {
        return "methodPublic";
    }

    public static String methodPublicStatic() {
        return "methodPublicStatic";
    }
    
    public String callPraivateMethod(){
        return new JmockitSample().methodPrivate();
    }
    
    private String methodPrivate() {
        return "methodPrivate";
    }

    public static String callPrivateStaticMethod(){
        return JmockitSample.methodPrivateStatic();
    }
    private static String methodPrivateStatic() {
        return "methodPrivateStatic";
    }
}

実行結果

methodPublic
methodPublicStatic
methodPrivate
methodPrivateStatic

テストコード内で4つのメソッドを前もmockに置き換えて、 Mocked{メソッド名}に変更してみる

package test.jmockit;

import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
import mockit.Mock;
import mockit.MockUp;

import org.junit.Test;

public class JmockitSampleTest {

    @Test
    public void mockTest(){

        new MockUp<JmockitSample>() {

            @Mock
            public String methodPublic() {
                return "mocked methodPublic";
            }

            @Mock
            public String methodPublicStatic() {
                return "mocked methodPublicStatic";
            }

            @Mock
            private String methodPrivate() {
                return "mocked methodPrivate";
            }

            @Mock
            private String methodPrivateStatic() {
                return "mocked methodPrivateStatic";
            }
        };

        System.out.println(new JmockitSample().methodPublic()      );
        System.out.println(JmockitSample.methodPublicStatic()      );
        System.out.println(new JmockitSample().callPraivateMethod());
        System.out.println(JmockitSample.callPrivateStaticMethod() );
        
        assertThat(new JmockitSample().methodPublic(), is("mocked methodPublic"));
        assertThat(JmockitSample.methodPublicStatic(), is("mocked methodPublicStatic"));
        assertThat(new JmockitSample().callPraivateMethod(), is("mocked methodPrivate"));
        assertThat(JmockitSample.callPrivateStaticMethod(), is("mocked methodPrivateStatic"));

    }
}

結果

mocked methodPublic
mocked methodPublicStatic
mocked methodPrivate
mocked methodPrivateStatic

注意点

クラスパスがjunitよりも先に来てないとうまく動かないので、その点だけ注意して下さい。 Eclipseだとこんな感じにすればOK f:id:kogayushi:20150607105459p:plain

gradleならこんな感じ

apply plugin: 'java'
apply plugin: 'eclipse'

sourceCompatibility = '1.7'
[compileJava, compileTestJava]*.options*.encoding = 'UTF-8'

configurations {
    jmockit_agent
}

repositories {
    mavenCentral()
}

dependencies {
    jmockit_agent 'com.googlecode.jmockit:jmockit:1.7'
    testCompile 'com.googlecode.jmockit:jmockit:1.7'
    testCompile 'junit:junit:4.11'
    
}

test {
    jvmArgs "-javaagent:${configurations.jmockit_agent.asPath}"
}

ちなみにNetBeansの場合は、gradle使ってる場合はgradleに完全にコンパイル任せてるのでbulid.gradleの設定さえしておけば、うまく動きます。

参考にしたサイトその1 knjname.hateblo.jp

参考にしたサイトその2 gist.github.com

Base64でデコード出来ない文字をデコードしようとするとどうなるか?

調べても出てこなかったので、メモ。

何かわかりやすいエラーが出るのかとおもいきや、エラーとわかりやすい挙動はしなかった…orz

答えは『実装による』っぽい。 実際に平文→base64.decode→sha256でhash→base64.encodeの処理をOpensslとJavaの両方で試したら挙動に違いがった。

例えばOpenSSLだとこうなる

$ echo "aaaa" | openssl base64 -d | openssl sha -sha256 -binary | openssl base64
# u6nbXovptodruQ0AGBFeI/x0G6a/IyXn/PiO/tdQxMc=
$ echo "aaaa,,,," | openssl base64 -d | openssl sha -sha256 -binary | openssl base64
# 47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=

aaaaの場合とaaaa,,,,の場合で結果が異なる。 ,base64で使用していない文字なのでデコード出来ない。 そのため、,が入るとエラーになって、その結果毎回同じ文字列が返ってきている(ように見える)。

試しにaaaaaaaa,も試してみる

$ echo "aaaaaaa," | openssl base64 -d | openssl sha -sha256 -binary | openssl base64
# 47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=

やっぱり47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=になる。

なぜ47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=が返ってくるかはOpenSSLの中身を見ていないのでわからない。

Apache Commons Codecだとこうなる

package Base64Test;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import org.apache.commons.codec.binary.Base64;

public class Base64TestExecutor {
    public static void main(String[] agrs) throws NoSuchAlgorithmException{
        String target1 = "aaaa";
        String target2 = "aaaa,,,,";
        String result1 = testDigest(target1);
        String result2 = testDigest(target2);
        System.out.println("result1 : " + result1);
        System.out.println("result2 : " + result2);
        System.out.println("isEqual : " + result1.equals(result2));
    }

    private static String testDigest(String targetString) throws NoSuchAlgorithmException {
        
        byte[] testResult = Base64.decodeBase64(targetString);
        
        MessageDigest md = MessageDigest.getInstance("SHA-256");
        byte[] digestVal = md.digest(testResult);
        String encPassword = Base64.encodeBase64String(digestVal);
        
        return encPassword;
    }
}

実行結果

run:
result1 : u6nbXovptodruQ0AGBFeI/x0G6a/IyXn/PiO/tdQxMc=
result2 : u6nbXovptodruQ0AGBFeI/x0G6a/IyXn/PiO/tdQxMc=
isEqual : true
ビルド成功(合計時間: 0秒)

こっちは中身を見てみた。

    /**
     * <p>
     * Decodes all of the provided data, starting at inPos, for inAvail bytes. Should be called at least twice: once
     * with the data to decode, and once with inAvail set to "-1" to alert decoder that EOF has been reached. The "-1"
     * call is not necessary when decoding, but it doesn't hurt, either.
     * </p>
     * <p>
     * Ignores all non-base64 characters. This is how chunked (e.g. 76 character) data is handled, since CR and LF are
     * silently ignored, but has implications for other bytes, too. This method subscribes to the garbage-in,
     * garbage-out philosophy: it will not check the provided data for validity.
     * </p>
     * <p>
     * Thanks to "commons" project in ws.apache.org for the bitwise operations, and general approach.
     * http://svn.apache.org/repos/asf/webservices/commons/trunk/modules/util/
     * </p>
     *
     * @param in
     *            byte[] array of ascii data to base64 decode.
     * @param inPos
     *            Position to start reading data from.
     * @param inAvail
     *            Amount of bytes available from input for encoding.
     * @param context
     *            the context to be used
     */
    @Override
    void decode(final byte[] in, int inPos, final int inAvail, final Context context) {
        if (context.eof) {
            return;
        }
        if (inAvail < 0) {
            context.eof = true;
        }
        for (int i = 0; i < inAvail; i++) {
            final byte[] buffer = ensureBufferSize(decodeSize, context);
            final byte b = in[inPos++];
            if (b == pad) {
                // We're done.
                context.eof = true;
                break;
            } else {
                if (b >= 0 && b < DECODE_TABLE.length) {
                    final int result = DECODE_TABLE[b];
                    if (result >= 0) {
                        context.modulus = (context.modulus+1) % BYTES_PER_ENCODED_BLOCK;
                        context.ibitWorkArea = (context.ibitWorkArea << BITS_PER_ENCODED_BYTE) + result;
                        if (context.modulus == 0) {
                            buffer[context.pos++] = (byte) ((context.ibitWorkArea >> 16) & MASK_8BITS);
                            buffer[context.pos++] = (byte) ((context.ibitWorkArea >> 8) & MASK_8BITS);
                            buffer[context.pos++] = (byte) (context.ibitWorkArea & MASK_8BITS);
                        }
                    }
                }
            }
        }

        // Two forms of EOF as far as base64 decoder is concerned: actual
        // EOF (-1) and first time '=' character is encountered in stream.
        // This approach makes the '=' padding characters completely optional.
        if (context.eof && context.modulus != 0) {
            final byte[] buffer = ensureBufferSize(decodeSize, context);

            // We have some spare bits remaining
            // Output all whole multiples of 8 bits and ignore the rest
            switch (context.modulus) {
//              case 0 : // impossible, as excluded above
                case 1 : // 6 bits - ignore entirely
                    // TODO not currently tested; perhaps it is impossible?
                    break;
                case 2 : // 12 bits = 8 + 4
                    context.ibitWorkArea = context.ibitWorkArea >> 4; // dump the extra 4 bits
                    buffer[context.pos++] = (byte) ((context.ibitWorkArea) & MASK_8BITS);
                    break;
                case 3 : // 18 bits = 8 + 8 + 2
                    context.ibitWorkArea = context.ibitWorkArea >> 2; // dump 2 bits
                    buffer[context.pos++] = (byte) ((context.ibitWorkArea >> 8) & MASK_8BITS);
                    buffer[context.pos++] = (byte) ((context.ibitWorkArea) & MASK_8BITS);
                    break;
                default:
                    throw new IllegalStateException("Impossible modulus "+context.modulus);
            }
        }
    }

final int result = DECODE_TABLE[b]; の箇所でDECODE出来ない文字の場合は-1が返される。 そのため、result >= 0falseになるので、その文字は読み飛ばされる。 その結果、例えばaa,,,の結果はおなじになる。

こういうのは実際に試して動作確認して、中身のソースまで見てみないとわかんないですね!

HTML/CSS用テキストエディタ導入メモ~SublimeText~

趣味アプリ作るのに、HTMLでMOCK作ると思います。

その時、Eclipseでも出来るんですけどHTMLとCSSだけ書くのに(あともしかしたらJavascriptも)いいエディタないかなと思って探してみたらSublimeTextというのを見つけました。

カスタマイズしなくても好きなPlugin適当に入れれば使いやすくなりそうなのでちょっと使ってみます。 で、参考にしたサイトをメモのためにペタペタ。

テキストエディタ選びに迷ったらコレ!SublimeTextがすごい | Code部

catcher-in-the-tech.net

WordPress高速化~オブジェクトキャッシュ編~

友人の会社のサイトが遅いというので、高速化を頼まれて以下を実施したところ劇的に改善した!のでメモっておく。 ※表示に10秒以上かかっていたのが3秒台にまで改善

topコマンドでリソース監視してみたところ、負荷かけたらCPUのwaitがかなり高かったのでCPUがボトルネックになってると判断したのでPHPのオブジェクトキャッシュを導入した。 ※たった2-3リクエストでwaitの値が増えちゃう…。WordPressってすごく重いですね。

PHPは5.3なので、apcというものになるらしい。 ※調べたところそれ以降のバージョンでは標準でopcacheってのがあるらしいのでINIの設定を追加するだけで使える。

参考にしたサイト www.webcreator-net.com

また、Apacheのチューニングもした。 やったのはtop -d1コマンド叩いてterminalのswapのusedの値見ながら、swapしないぎりぎりの設定を探った。

やっぱり、サイトがさくさく動くと気持ちいいですね。

WordPressの外観のカスタマイズが開けない

という、相談を友人から受けたので調査した。

その結果、Pluginの組み合わせが悪い時にこの現象が起きているようだ。 ソース読んでないので症状からの判断だが、

Page Builder by SiteOrigin && (Dynamic Widgets || WP Widget Cache) の条件の時に起きるみたい。 ※日本語でわかりやすく書くのがめんどくさいからJavaっぽい文法で表現してみた

あと、キャッシュPluginのせいで管理バーがサイトに表示されちゃってたので、 functions.phpadd_filter( 'show_admin_bar', '__return_false' ); の1行を追加して対応。

WordPress3.1からサイトにも管理バー表示できるようにしたらしいけど、キャッシュ系Pluginと相性悪いみたい。

ってな感じの調査をしてたら疲れたので今日はここまでなり。

さあ、本職のためにJavaの勉強するぞ!!っと。