顯示具有 JUnit 標籤的文章。 顯示所有文章
顯示具有 JUnit 標籤的文章。 顯示所有文章

2013年1月31日 星期四

Unit Test to Struts2 Action with Spring

我將展示使用JUnit3和JUnit4兩種測試Struts2 Action的寫法,兩種都會enable Spring以及所有Struts2 plugins。

JUnit3:
TestActionJUnit3Test
import static org.apache.commons.lang.StringUtils.replace;
import lombok.SneakyThrows;
import org.apache.struts2.StrutsSpringTestCase;
import com.opensymphony.xwork2.ActionProxy;

public class TestActionJUnit3Test extends StrutsSpringTestCase {

    @SneakyThrows
    public void test() {
        ActionProxy proxy = getActionProxy("/test/test.action");
        String result = proxy.execute();
        assertEquals("success", result);
        assertEquals("Test by Linus", ((TestAction) proxy.getAction()).getMessage());

        executeAction("/test/test");
        assertFalse(((TestAction) findValueAfterExecute("action")).hasFieldErrors());
        assertEquals("Test by Linus", findValueAfterExecute("message"));
    }

    @Override
    protected String[] getContextLocations() {
        return new String[] { "classpath:" + replace(getClass().getName(), ".", "/") + "-context.xml" };
    }

}
上面我將預設讀取的Spring XML路徑改成和@ContextConfiguration一致。

JUnit4:
TestActionJUnit4Test
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import org.apache.struts2.StrutsSpringJUnit4TestCase;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import com.opensymphony.xwork2.ActionProxy;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
public class TestActionJUnit4Test extends StrutsSpringJUnit4TestCase<TestAction> {

    @Override
    protected String getConfigPath() {
        return "struts-plugin.xml";
    }

    @Test
    public void testExecute() throws Exception {
        ActionProxy proxy = getActionProxy("/test/test.action");
        String result = proxy.execute();
        assertEquals("success", result);
        assertEquals("Test by Linus", ((TestAction) proxy.getAction()).getMessage());

        executeAction("/test/test");
        assertFalse(getAction().hasFieldErrors());
        assertEquals("Test by Linus", findValueAfterExecute("message"));
    }

}
上面getConfigPath()可以回傳其它Struts2的XML檔,利用這個method可以啟動其它plugins,但JUnit3的寫法不需要這個動作。

2013年1月29日 星期二

Data Driven Testing by JUnit Theories

JUnit Theories是另外一種實現Data Driven Testing的方式,它的強項在於它會自動排列組合所有@DataPoint和@DataPoints的data傳入test method裡面,然後我們可以搭配Assume過濾掉一些不合理的情境。

以下是一個使用Theories的test case:
IdentificationNumberValidatorTheoriesTest
package com.gss.gmo.cao.validator.constraints.impl;

import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assume.assumeFalse;
import static org.junit.Assume.assumeTrue;

import java.util.ArrayList;
import java.util.Collection;

import org.junit.BeforeClass;
import org.junit.experimental.theories.DataPoints;
import org.junit.experimental.theories.Theories;
import org.junit.experimental.theories.Theory;
import org.junit.runner.RunWith;

@RunWith(Theories.class)
public class IdentificationNumberValidatorTheoriesTest {

    @DataPoints
    public static DataBean[] identificationNumber;

    @BeforeClass
    public static void prepareData() {
        Collection<DataBean> data = new ArrayList<DataBean>();
        data.add(new DataBean(true, null));
        data.add(new DataBean(false, ""));
        data.add(new DataBean(false, "1qazxsw2"));
        data.add(new DataBean(true, "H120178472"));
        data.add(new DataBean(true, "h120178472"));
        data.add(new DataBean(false, "h120178470"));
        identificationNumber = data.toArray(new DataBean[] {});
    }

    private IdentificationNumberValidator identificationNumberValidator = new IdentificationNumberValidator();

    @Theory
    public void testTrue(DataBean data) {
        assumeTrue(data.isResult());
        assertTrue(identificationNumberValidator.isValid(data.getIdentificationNumber(), null));
    }

    @Theory
    public void testFalse(DataBean data) {
        assumeFalse(data.isResult());
        assertFalse(identificationNumberValidator.isValid(data.getIdentificationNumber(), null));
    }

    static class DataBean {

        private final boolean result;

        private final String identificationNumber;

        DataBean(boolean result, String identificationNumber) {
            this.result = result;
            this.identificationNumber = identificationNumber;
        }

        public boolean isResult() {
            return result;
        }

        public String getIdentificationNumber() {
            return identificationNumber;
        }

    }
}

Data Driven Testing by JUnit Parameterized Tests

本文直接展示兩種unit test的寫法來比較JUnit Parameterized Tests的好處在哪。

首先是傳統的寫法:
UnifiedBusinessNumberValidatorTest
package com.gss.gmo.cao.validator.constraints.impl;

import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;

import org.junit.Test;

public class UnifiedBusinessNumberValidatorTest {

    @Test
    public void testIsValid() {
        UnifiedBusinessNumberValidator unifiedBusinessNumberValidator = new UnifiedBusinessNumberValidator();
        assertTrue(unifiedBusinessNumberValidator.isValid(null, null));
        assertFalse(unifiedBusinessNumberValidator.isValid("", null));
        assertFalse(unifiedBusinessNumberValidator.isValid("12345678", null));
        assertTrue(unifiedBusinessNumberValidator.isValid("00651474", null));
        assertFalse(unifiedBusinessNumberValidator.isValid("00651479", null));
        assertTrue(unifiedBusinessNumberValidator.isValid("22425662", null));
        assertFalse(unifiedBusinessNumberValidator.isValid("22425669", null));
        assertTrue(unifiedBusinessNumberValidator.isValid("28706210", null));
        assertTrue(unifiedBusinessNumberValidator.isValid("53112454", null));
        assertTrue(unifiedBusinessNumberValidator.isValid("04607774", null));
        assertTrue(unifiedBusinessNumberValidator.isValid("22446771", null));
        assertTrue(unifiedBusinessNumberValidator.isValid("22515072", null));
        assertTrue(unifiedBusinessNumberValidator.isValid("22595770", null));
        assertTrue(unifiedBusinessNumberValidator.isValid("22227375", null));
        assertTrue(unifiedBusinessNumberValidator.isValid("07006628", null));
    }

}
執行結果:

接下來我們利用JUnit Parameterized Tests的寫法改寫一次這個unit test:
UnifiedBusinessNumberValidatorParameterizedTest
package com.gss.gmo.cao.validator.constraints.impl;

import static org.junit.Assert.assertEquals;

import java.util.ArrayList;
import java.util.Collection;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;

/**
 * @author linus_chien
 *
 */
@RunWith(value = Parameterized.class)
public class UnifiedBusinessNumberValidatorParameterizedTest {

    @Parameters(name = "[{index}]: [{1}] is valid? [{0}]")
    public static Collection<Object[]> data() {
        Collection<Object[]> data = new ArrayList<Object[]>();
        data.add(new Object[] { Boolean.TRUE, null });
        data.add(new Object[] { Boolean.FALSE, "" });
        data.add(new Object[] { Boolean.FALSE, "12345678" });
        data.add(new Object[] { Boolean.TRUE, "00651474" });
        data.add(new Object[] { Boolean.FALSE, "00651479" });
        data.add(new Object[] { Boolean.TRUE, "22425662" });
        data.add(new Object[] { Boolean.FALSE, "22425669" });
        data.add(new Object[] { Boolean.TRUE, "28706210" });
        data.add(new Object[] { Boolean.TRUE, "53112454" });
        data.add(new Object[] { Boolean.TRUE, "04607774" });
        data.add(new Object[] { Boolean.TRUE, "22446771" });
        data.add(new Object[] { Boolean.TRUE, "22515072" });
        data.add(new Object[] { Boolean.TRUE, "22595770" });
        data.add(new Object[] { Boolean.TRUE, "22227375" });
        data.add(new Object[] { Boolean.TRUE, "07006628" });
        return data;
    }

    private final boolean result;

    private final String unifiedBusinessNumber;

    public UnifiedBusinessNumberValidatorParameterizedTest(Boolean result, String unifiedBusinessNumber) {
        this.result = result;
        this.unifiedBusinessNumber = unifiedBusinessNumber;
    }

    @Test
    public void test() {
        UnifiedBusinessNumberValidator unifiedBusinessNumberValidator = new UnifiedBusinessNumberValidator();
        assertEquals(result, unifiedBusinessNumberValidator.isValid(unifiedBusinessNumber, null));
    }

}
執行結果:

從執行結果我們可以發現每一筆資料都變成一個test case,而且可以從名稱上了解測試資料內容,這樣在test case failed的時候可以很清楚知道是哪筆資料出錯,從報表上就可以一目瞭然。

須注意的是@Parameters可以下name attribute是4.11版之後才支援。