單元測試(3)junit特性

junit特性

項目多元化,導(dǎo)致最基本的功能有時難以應(yīng)付决采。所以栖榨,junit自4.x發(fā)布以來昆汹,每次新版本的推出都引入很多測試?yán)砟詈蜋C(jī)制。在實際項目之中婴栽,不一定非要用到所有的這些特性满粗,選擇適合的才是最好的。

基本功能回顧

  • @Test愚争,最小測試方法注釋
  • @BeforeClass映皆,測試類執(zhí)行前操作挤聘,用于初始化,必須是public static void捅彻,且放在類的開始處
  • @AfterClass组去,測試類執(zhí)行后操作,用于清理步淹、釋放从隆,必須是public static void,且放在類的開始處
  • @Before缭裆,@Test方法執(zhí)行前邏輯
  • @After键闺,@Test方法執(zhí)行后邏輯
  • @Ignored,忽略@Test方法執(zhí)行

斷言機(jī)制及斷言增強(qiáng)

斷言是單元測試最基本澈驼,最核心艾杏,最重要的概念。我們可以將斷言理解為一個語句是否成立盅藻。junit最初的斷言,做了最基本的功能——兩個值是相同畅铭、結(jié)果是否為空指針氏淑、斷言語句是否為True(或者False),且增加失敗時的信息輸出硕噩。

通用格式如下(謂主賓格式):

Assert.assertEquals([optional]message, expected, actual);

完整實例如下:

@Test
public void testAssert() {
    Assert.assertEquals(1L, 1L);
    Assert.assertNotEquals(1L, 2L);

    Assert.assertNotNull(new Object());
    Assert.assertNull(null);

    Assert.assertTrue(true);
    Assert.assertFalse(false);

    Assert.assertNotSame("the two objects not same", new Object(), new Object());

    final Object sameObj = new Object();
    Assert.assertSame("sameObj is not same of sameObj", sameObj, sameObj);

    final int[] expecteds = {1, 2, 3};
    final int[] actuals = {1, 2, 3};
    Assert.assertArrayEquals(expecteds, actuals);
}

這種斷言機(jī)制引入之后假残,極大地增強(qiáng)了代碼的可讀性和完整性。不過炉擅,事情總是朝著好的方向發(fā)展辉懒。junit社區(qū)有人反饋,認(rèn)為這種反人類語言的格式不是很好用谍失,junit作者也善意地采納其意見眶俩,并決定在后期版本中加入更易懂的方式(主謂賓格式),以便更親近人類快鱼,語義也更直觀颠印。所以,引入了assertThat抹竹,它使用Matcher(匹配器)來完成它的職責(zé)线罕,Matcher本質(zhì)是使用鏈?zhǔn)骄幊痰姆绞綄崿F(xiàn)引用代入。核心的Matcher都放在org.hamcrest.CoreMatchers下面的窃判。

通用格式如下:

assertThat(actual, Matcher<? super T> matcher);

完整實例如下:

@Test
public void testAssertThat() {
    final int id = 3;
    Assert.assertThat(id, is(3));
    Assert.assertThat(id, is(not(4)));

    final boolean trueValue = true;
    Assert.assertThat(trueValue, is(true));

    final boolean falseValue = false;
    Assert.assertThat(falseValue, is(false));

    final Object nullObject = null;
    Assert.assertThat(nullObject, nullValue());

    final String helloWord = "Hello xxx World";
    Assert.assertThat(helloWord, both(startsWith("Hello")).and(endsWith("World")));
}

自定義Matcher

有時钞楼,junit自帶的Matcher并不能完成我們想要完成的匹配,這時我們就需要自定義Matcher袄琳,以此來用于特定語境下的特定處理询件。org.hamcrest.Matcher是一個接口燃乍,它的注釋上明確寫明,不能直接繼承它雳殊,需要繼承org.hamcrest.BaseMatcher橘沥。引用其注釋如下:

Matcher implementations <span style="color:red;">should NOT directly implement this interface</span>. Instead, <span style="color:red;">extend the BaseMatcher abstract class</span>, which will ensure that the Matcher API can grow to support new features and remain compatible with all Matcher implementations.

舉例來說,我們想要實現(xiàn)這樣的Matcher夯秃,用于判斷User對象的username和password都是admin座咆。這種應(yīng)用場景雖然比較BT,但是也是有可能的仓洼,這兒我們實現(xiàn)自己的Matcher介陶,代碼如下:

class User {
    private String username;
    private String password;
    // omited...
}
/**
 * <p>
 * Matcher implementations should <b>NOT directly implement this interface</b>.
 * Instead, <b>extend</b> the {@link BaseMatcher} abstract class,
 * which will ensure that the Matcher API can grow to support
 * new features and remain compatible with all Matcher implementations.
 * <p/>
 * @see org.hamcrest.Matcher
 */
class IsAdminMatcher extends BaseMatcher<User> {

    @Override
    public boolean matches(Object item) {
        if (item == null) {
            return false;
        }

        User user = (User) item;
        return "admin".equals(user.getUsername()) && "admin".equals(user.getPassword());
    }

    /**
     * real description about the actual value
     *
     * @param description the simple description obj
     */
    @Override
    public void describeTo(Description description) {
        description.appendText("Administrator with 'admin' as username and password");
    }

    /**
     * while meeting assert fail, it will be printed out
     *
     * @param item actual value
     * @param description the simple description obj
     */
    @Override
    public void describeMismatch(Object item, Description description) {
        if (item == null) {
            description.appendText("was null");
        } else {
            User user = (User) item;
            description.appendText("was a common user (")
                    .appendText("username: ").appendText(user.getUsername()).appendText(", ")
                    .appendText("password: ").appendText(user.getPassword()).appendText(")");
        }
    }
}
User user = new User("admin", "admin");
Assert.assertThat(user, new IsAdminMatcher());

test方法執(zhí)行順序

在測試類中,如果我們要指定方法的執(zhí)行順序色建,可以使用注解FixMethodOrder哺呜。這樣,test case不會亂序執(zhí)行箕戳。樣例代碼如下:

@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class MethodOrderTest {

    @Test
    public void testA() {
        System.out.println("first");
    }

    @Test
    public void testB() {
        System.out.println("second");
    }

    @Test
    public void testC() {
        System.out.println("third");
    }
}

MethodSorter是一個枚舉某残,它有以下枚舉項,默認(rèn)為DEFAULT陵吸。

public enum MethodSorters {
    /**
     * 按照字母升序執(zhí)行
     */
    NAME_ASCENDING(MethodSorter.NAME_ASCENDING),

    /**
     * 按照J(rèn)VM中方法加載順序執(zhí)行
     */
    JVM(null),

    /**
     * 默認(rèn)順序玻墅,由方法名hashcode值來決定,如果hash值大小一致壮虫,則按名字的字典順序確定澳厢。
     * 由于hashcode的生成和操作系統(tǒng)相關(guān)(以native修飾),所以對于不同操作系統(tǒng)囚似,可能會出現(xiàn)不一樣的執(zhí)行順序剩拢。
     * 在某一操作系統(tǒng)上,多次執(zhí)行的順序不變饶唤。
     */
    DEFAULT(MethodSorter.DEFAULT);
}

suite徐伐,聚合test cases

suite,顧名思義募狂,就是套件的意思呵晨。在junit中,它主要用于將一堆test cases聚合起來熬尺,形成一個套件摸屠。有兩種suite使用方式,一種為硬編碼方式粱哼,一種為注解方式季二。

【注】:可以將TestSuite看成一種特殊的Test。從代碼層面我們也可以看出,TestSuite繼承了Test胯舷。

首先刻蚯,假設(shè)我們有以下的test cases:

public class DemoTest {

    public static class TestSuite2 {
        public static junit.framework.Test suite() {
            TestSuite suite = new TestSuite("Test for package2");

            suite.addTest(new JUnit4TestAdapter(Test4.class));
            suite.addTest(new JUnit4TestAdapter(Test5.class));
            return suite;
        }
    }

    public static class Test1 {

        @Test
        public void test1() {
            System.out.println("test1 invoked");
        }
    }

    public static class Test2 {

        @Test
        public void test2() {
            System.out.println("test2 invoked");
        }
    }

    public static class Test3 {

        @Test
        public void test3() {
            System.out.println("test3 invoked");
        }
    }

    public static class Test4 {

        @Test
        public void test4() {
            System.out.println("test4 invoked");
        }
    }

    public static class Test5 {

        @Test
        public void test5() {
            System.out.println("test5 invoked");
        }
    }
}

其中TestSuite2可以看成是Test4和Test5的組合,現(xiàn)在我們想將Test1桑嘶、Test2炊汹、Test3和TestSuite2組合起來一起執(zhí)行,應(yīng)該怎么辦呢逃顶?這時候就是suite上場的時候了讨便。

1、硬編碼方式

public class Suite1Test {

    public static Test suite() {
        TestSuite suite = new TestSuite("Test for package1");

        suite.addTest(new JUnit4TestAdapter(DemoTest.Test1.class));
        suite.addTest(new JUnit4TestAdapter(DemoTest.Test2.class));
        suite.addTest(new JUnit4TestAdapter(DemoTest.Test3.class));

        suite.addTest(DemoTest.TestSuite2.suite());
        return suite;
    }
}

2以政、注解方式

@RunWith(Suite.class)
@Suite.SuiteClasses({
        DemoTest.Test1.class,
        DemoTest.Test2.class,
        DemoTest.Test3.class,
        DemoTest.TestSuite2.class
})
public class Suite2Test {
}

綜合分析霸褒,注解方式代碼更少、更簡潔盈蛮,在實際項目中废菱,我們更偏向使用注解方式。另外需要注意的是抖誉,TestSuite應(yīng)該有一個public static junit.framework.Test suite()方法殊轴。

參數(shù)化測試

它可以看做是suite的一種特例,目的是為了對test case進(jìn)行多次執(zhí)行袒炉,已達(dá)到較為全面的覆蓋梳凛。參數(shù)化測試需要以一種特殊的Runner執(zhí)行,@RunWith(Parameterized.class)梳杏。下面我們以斐波那契來舉例。

關(guān)于什么是斐波那契序列淹接,請移步<a target="_blank">百度百科</a>十性。

@RunWith(Parameterized.class)
public class FibonacciTest {

    /**
     * In order to easily identify the individual test cases in a Parameterized test,
     * you may provide a name using the @Parameters annotation.
     * This name is allowed to contain placeholders that are replaced at runtime:<br>
     * {index}: the current parameter index<br>
     * {0}, {1}, …: the first, second, and so on, parameter value. NOTE: single quotes ' should be escaped as two single quotes ''.<br>
     * <p/>
     * In the example given above, the Parameterized runner creates names like [1: fib(3)=2].
     * If you don't specify a name, the current parameter index will be used by default.
     *
     * @return
     */
    //@Parameterized.Parameters
    @Parameterized.Parameters(name = "{index}: fib({0})={1}")
    public static Collection data() {
        return Arrays.asList(new Object[][]{{0, 0}, {1, 1}, {2, 1}, {3, 2}, {4, 3}, {5, 5}, {6, 8}});
    }

    private int input;
    private int expected;

    public FibonacciTest(int input, int expected) {
        this.input = input;
        this.expected = expected;
    }

    @Test
    public void test() {
        Assert.assertEquals(expected, Fibonacci.compute(input));
    }
}

這兒我們可以給參數(shù)取一個名字坏逢,一般情況下使用默認(rèn)的就可以了季稳。

個人覺得,參數(shù)化測試帶來了一些弊端——如果有多個test case需要進(jìn)行參數(shù)化作喘,需要增加至多個測試類厢蒜。粒度為類霞势,而不是方法。后面的特性中斑鸦,我們會介入解決這種問題愕贡。

自定義Rule

自定義規(guī)則的意圖是為了豐富test case,增加其靈活性巷屿。我們可以簡單地羅列一些場景:

  • 循環(huán)執(zhí)行N次固以,N為一個變量;
  • 滿足條件時,執(zhí)行M次憨琳,不滿足條件時诫钓,執(zhí)行N次
  • 循環(huán)執(zhí)行篙螟,直到條件滿足時跳出循環(huán)菌湃。

這些場景中,如果我們把test case看成一個單元遍略,規(guī)則則是在filter這個單元之后惧所,對其進(jìn)行判斷、循環(huán)和額外邏輯處理的Runner墅冷。極大地滿足了某些應(yīng)用場景纯路,且增加了單元測試的靈活性。

下面我們以一個例子來演示:

1寞忿、定義規(guī)則注解

@Retention(RetentionPolicy.RUNTIME)
@Target({java.lang.annotation.ElementType.METHOD})
public @interface Repeat {
    int times();
}

2驰唬、定義規(guī)則

class RepeatRule implements TestRule {

    private static class RepeatStatement extends Statement {

        private final int times;
        private final Statement statement;

        private RepeatStatement(int times, Statement statement) {
            this.times = times;
            this.statement = statement;
        }

        @Override
        public void evaluate() throws Throwable {
            for (int i = 0; i < times; i++) {
                statement.evaluate();
            }
        }
    }

    @Override
    public Statement apply(Statement statement, Description description) {
        Statement result = statement;
        Repeat repeat = description.getAnnotation(Repeat.class);
        if (repeat != null) {
            int times = repeat.times();
            result = new RepeatStatement(times, statement);
        }
        return result;
    }
}

3、使用規(guī)則

public class TestRuleTest {

    @Rule
    public RepeatRule repeatRule = new RepeatRule();

    @Test
    @Repeat(times = 100)
    public void testCalculateRangeValue() {
        long center = 0;
        long radius = 10;
        RandomRangeValueCalculator calculator = new RandomRangeValueCalculatorImpl();

        long actual = calculator.calculateRangeValue(center, radius);
        System.out.println(actual);

        Assert.assertTrue(center + radius >= actual);
        Assert.assertTrue(center - radius <= actual);
    }
}

分組測試-Categories

分組測試腔彰,其實也屬于一種特殊的suite叫编,用于對test case進(jìn)行分組。并使用@RunWith(Categories.class)來執(zhí)行和篩除分組的test case霹抛。下面以一個例子來清晰表明如何進(jìn)行分組測試搓逾。

1、定義分組

// 聲明兩個什么都沒有的接口
public interface FastTests { }
public interface SlowTests { }

2杯拐、書寫單元測試

public class ATest {

    @Test
    public void a() {
        System.out.println("A a()");
    }

    @Category(SlowTests.class)
    @Test
    public void b() {
        System.out.println("A b()");
    }
}

@Category({SlowTests.class, FastTests.class})
public class BTest {

    @Test
    public void c() {
        System.out.println("B c()");
    }
}

3霞篡、書寫分組suite

@RunWith(Categories.class) // 這個地方與一般的套件測試有所不同
@Categories.IncludeCategory(SlowTests.class)
@Suite.SuiteClasses({
        ATest.class,
        BTest.class
}) // Note that Categories is a kind of Suite
public class SlowTestSuite1 {
}
@RunWith(Categories.class)
@Categories.IncludeCategory(SlowTests.class)
@Categories.ExcludeCategory(FastTests.class)
@Suite.SuiteClasses({
        ATest.class,
        BTest.class
})
public class SlowTestSuite2 {
}

假設(shè)機(jī)制-assume

假設(shè)機(jī)制是用于在條件滿足時執(zhí)行test case,條件不滿足時忽略test case的特殊機(jī)制端逼。它使用assumeThat來進(jìn)行判斷朗兵。

public class AssumeTest {

    @Test
    public void testOneEqualsOne() {
        // sample for actual
        // assumeThat(File.separatorChar, is('/'));
        // System.out.println("is executed");
        assumeThat('1', is('1'));
        System.out.println("1 == 1");
    }

    @Test
    public void testOneNotEqualsTwo() {
        assumeThat('1', is('2'));
        System.out.println("1 == 2");
    }
}

理論機(jī)制-Theories

Theories,英文意思為理論顶滩、推斷余掖。它是一種特殊的Runner,提供了除Parameterized之外的另外一個更為強(qiáng)大的參數(shù)化測試解決方案礁鲁。Theories不是使用帶參的構(gòu)造方法盐欺,而是使用受參的測試方法。test case的修飾注解也從@Test變化為@Theory仅醇,參數(shù)的提供也變化為@DataPoint或者@Datapoints冗美,他們兩的不同之處在于前者代表一個數(shù)據(jù),后者代表一組數(shù)據(jù)析二。Theories會嘗試所有類型匹配的參數(shù)作為測試方法的入?yún)⒍昭谩N覀兣e一個簡單的例子。

@RunWith(Theories.class)
public class UserTest {

    @DataPoint
    public static String GOOD_USERNAME = "optimus";
    @DataPoint
    public static String USERNAME_WITH_SLASH = "optimus/prime";
    @DataPoints
    public static String[] usernames = {"optimus", "optimus/prime"};

    @Theory
    public void filenameIncludesUsername(String username) {
        assumeThat(username, not(containsString("/")));
        assertThat(username, is(GOOD_USERNAME));
        assertThat(new User(username).configFileName(), containsString(username));
    }

    static class User {

        private String username;

        public User(String username) {
            this.username = username;
        }

        public String getUsername() {
            return username;
        }

        public void setUsername(String username) {
            this.username = username;
        }

        public String configFileName() {
            return username + "/sb";
        }
    }
}

因為有assumeThat(username, not(containsString("/")));,所以只有不帶“/”的參數(shù)才會被代入漆改。

Theories還支持了自定義數(shù)據(jù)提供方式心铃,需要繼承Junit的ParameterSupplier。

1挫剑,定義參數(shù)注解

@Retention(RetentionPolicy.RUNTIME)
@ParametersSuppliedBy(BetweenSupplier.class)
@interface Between {
    int first();

    int last();
}

2去扣,提供參數(shù)支持類,繼承ParameterSupplier類

public class BetweenSupplier extends ParameterSupplier {

    @Override
    public List<PotentialAssignment> getValueSources(ParameterSignature sig) {
        Between annotation = sig.getAnnotation(Between.class);

        List<PotentialAssignment> list = new ArrayList<>();
        for (int i = annotation.first(); i <= annotation.last(); i++) {
            list.add(PotentialAssignment.forValue("value", i));
        }
        return list;
    }
}

3樊破、test case使用參數(shù)注解

@RunWith(Theories.class)
public class DollarTest {

    @Theory
    public void multiplyIsInverseOfDivideWithInlineDataPoints(@Between(first = -100, last = 100) int amount,
                                                              @Between(first = -100, last = 100) int m) {
        assumeThat(m, not(0));
        System.out.println(amount + ":" + m);
        assertThat(new Dollar(amount).times(m).divideBy(m).getAmount(), is(amount));
    }
}

Junit自帶了TestedOn注解愉棱,用于輸入一個int數(shù)組,樣例代碼如下:

@RunWith(Theories.class)
public class DollarTest {

    @Theory
    public final void test(@TestedOn(ints = {0, 1, 2}) int i) {
        assertTrue(i >= 0);
    }

    @Theory
    public void multiplyIsInverseOfDivide(@TestedOn(ints = {0, 5, 10}) int amount,
                                          @TestedOn(ints = {0, 1, 2}) int m) {
        assumeThat(m, not(0));
        assertThat(new Dollar(amount).times(m).divideBy(m).getAmount(), is(amount));
    }
}

多線程下的單元測試

在多線程下哲戚,單元測試很難保證線程安全奔滑。junit并沒有直接提供多線程環(huán)境下的測試機(jī)制,但是指明了使用某些第三方類庫可以達(dá)到這樣的目的顺少。concurrentunit就是其中的一種朋其,在gradle下面,我們可以使用compile 'net.jodah:concurrentunit:${version}'引入依賴脆炎。下面我們提供一個非常簡單的多線程示例梅猿,Waiter提供了類似CountDownLatch機(jī)制,關(guān)于什么是CountDownLatch秒裕,請自行百度袱蚓。

@Test
public void shouldSupportMultipleThreads() throws Throwable {
    final Waiter waiter = new Waiter();

    for (int i = 0; i < 5; i++) {
        new Thread(new Runnable() {
            public void run() {
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    System.out.println(e.getLocalizedMessage() + e);
                }
                waiter.assertTrue(true);
                waiter.resume();
            }
        }).start();
    }

    waiter.await();
}

參考文獻(xiàn)

junit百度百科

junit斷言和Matcher

執(zhí)行順序

suite

參數(shù)化測試

自定義Rule

分組測試

假設(shè)機(jī)制

理論機(jī)制

多線程下的junit

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市几蜻,隨后出現(xiàn)的幾起案子喇潘,更是在濱河造成了極大的恐慌,老刑警劉巖梭稚,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件颖低,死亡現(xiàn)場離奇詭異,居然都是意外死亡哨毁,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進(jìn)店門源武,熙熙樓的掌柜王于貴愁眉苦臉地迎上來扼褪,“玉大人,你說我怎么就攤上這事粱栖』敖剑” “怎么了?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵闹究,是天一觀的道長幔崖。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么赏寇? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任吉嫩,我火速辦了婚禮,結(jié)果婚禮上嗅定,老公的妹妹穿的比我還像新娘自娩。我一直安慰自己,他們只是感情好渠退,可當(dāng)我...
    茶點故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布忙迁。 她就那樣靜靜地躺著,像睡著了一般碎乃。 火紅的嫁衣襯著肌膚如雪姊扔。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天梅誓,我揣著相機(jī)與錄音恰梢,去河邊找鬼。 笑死证九,一個胖子當(dāng)著我的面吹牛删豺,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播愧怜,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼呀页,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了拥坛?” 一聲冷哼從身側(cè)響起蓬蝶,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎猜惋,沒想到半個月后丸氛,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡著摔,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年缓窜,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片谍咆。...
    茶點故事閱讀 38,161評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡禾锤,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出摹察,到底是詐尸還是另有隱情恩掷,我是刑警寧澤,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布供嚎,位于F島的核電站黄娘,受9級特大地震影響峭状,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜逼争,卻給世界環(huán)境...
    茶點故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一优床、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧氮凝,春花似錦羔巢、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至稿壁,卻和暖如春幽钢,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背傅是。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工匪燕, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人喧笔。 一個月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓帽驯,卻偏偏與公主長得像,于是被迫代替她去往敵國和親书闸。 傳聞我的和親對象是個殘疾皇子尼变,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,916評論 2 344

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn)浆劲,斷路器嫌术,智...
    卡卡羅2017閱讀 134,601評論 18 139
  • 簡介 測試 在軟件開發(fā)中是一個很重要的方面,良好的測試可以在很大程度決定一個應(yīng)用的命運牌借。軟件測試中度气,主要有3大種類...
    Whyn閱讀 5,726評論 0 2
  • 注意事項: 測試方法上必須使用@Test修飾 測試方法必須使用public void進(jìn)行修飾,不能帶任何參數(shù) 新建...
    魯克巴克詩閱讀 1,753評論 0 3
  • JUnit Intro Android基于JUnit Framework來書寫測試代碼膨报。JUnit是基于Java語...
    chandarlee閱讀 2,249評論 0 50
  • Spring Boot 參考指南 介紹 轉(zhuǎn)載自:https://www.gitbook.com/book/qbgb...
    毛宇鵬閱讀 46,748評論 6 342