提高單元測(cè)試用例覆蓋率

image

在軟件工程領(lǐng)域界阁,無論對(duì)于前端或是后端工程師都必須對(duì)自己的代碼質(zhì)量負(fù)責(zé)令漂,尤其是后端工程師疮跑,高度的單元測(cè)試覆蓋率是最有效的手段之一组贺。

以Java代碼為例,有著Junit祖娘、Mockito等開源單元測(cè)試框架失尖,并且非常便于測(cè)試集成。Golang自身便帶有單元測(cè)試模塊渐苏,都是為提高軟件質(zhì)量而設(shè)計(jì)的掀潮,優(yōu)秀的開源框架其單元測(cè)試覆蓋率都非常高,才能為其軟件質(zhì)量提供保障琼富。

一名優(yōu)秀的后端工程師必須具備編寫單元測(cè)試的習(xí)慣仪吧,而且測(cè)試覆蓋率越高你的軟件質(zhì)量也就越好,其次高度覆蓋的測(cè)試用例方便軟件的迭代與維護(hù)鞠眉,能夠有助于后者快速理解代碼與定位問題薯鼠。

高覆蓋體現(xiàn)在測(cè)試用例覆蓋public方法择诈、函數(shù)中的if-else等邏輯、參數(shù)檢查出皇、內(nèi)部定義等羞芍。

這里以Junit和Google的Truth為案例展示一個(gè)測(cè)試用例案例。

定義一個(gè)Resource實(shí)體類型郊艘,實(shí)體內(nèi)部定義了參數(shù)必要檢查荷科、public函數(shù):

/**
 * {@link Resource} represents a resource, which capture identifying information about the entities
 * for which signals (stats or traces) are reported.
 */
@Immutable
@AutoValue
public abstract class Resource {
    private static final int MAX_LENGTH = 255;
    private static final String ERROR_MESSAGE_INVALID_CHARS =
        " should be a ASCII string with a length greater than 0 and not exceed "
        + MAX_LENGTH
        + " characters.";
    private static final String ERROR_MESSAGE_INVALID_VALUE =
        " should be a ASCII string with a length not exceed " + MAX_LENGTH + " characters.";
    private static final Resource EMPTY = new AutoValue_Resource(Collections.emptyMap());

    Resource() {}

    public static Resource getEmpty() {
        return EMPTY;
    }

    /**
     * Returns a map of labels that describe the resource.
     *
     * @return a map of labels.
     */
    public abstract Map<String, String> getLabels();

    /**
     * Returns a {@link Resource}.
     *
     * @param labels a map of labels that describe the resource.
     * @return a {@code Resource}.
     * @throws NullPointerException if {@code labels} is null.
     * @throws IllegalArgumentException if label key or label value is not a valid printable ASCII
     *     string or exceed {@link #MAX_LENGTH} characters.
     */
    public static Resource create(Map<String, String> labels) {
        checkLabels(labels);
        return new AutoValue_Resource(labels);
    }

    /**
     * Returns a new, merged {@link Resource} by merging the current {@code Resource} with the
     * {@code other} {@code Resource}. In case of a collision, current {@code Resource} takes
     * precedence.
     *
     * @param other the {@code Resource} that will be merged with {@code this}.
     * @return the newly merged {@code Resource}.
     */
    public Resource merge(@Nullable Resource other) {
        if (other == null) {
            return this;
        }

        Map<String, String> mergedLabelMap = new LinkedHashMap<>(other.getLabels());
        // Labels from resource overwrite labels from otherResource.
        for (Map.Entry<String, String> entry : this.getLabels().entrySet()) {
            mergedLabelMap.put(entry.getKey(), entry.getValue());
        }
        return new AutoValue_Resource(mergedLabelMap);
    }

    private static void checkLabels(Map<String, String> labels) {
        for (Map.Entry<String, String> entry : labels.entrySet()) {
            Utils.checkArgument(
                isValidAndNotEmpty(entry.getKey()), "Label key" + ERROR_MESSAGE_INVALID_CHARS);
            Utils.checkArgument(
                isValid(entry.getValue()), "Label value" + ERROR_MESSAGE_INVALID_VALUE);
        }
    }

    /**
     * Determines whether the given {@code String} is a valid printable ASCII string with a length not
     * exceed {@link #MAX_LENGTH} characters.
     *
     * @param name the name to be validated.
     * @return whether the name is valid.
     */
    private static boolean isValid(String name) {
        return name.length() <= MAX_LENGTH && StringUtils.isPrintableString(name);
    }

    /**
     * Determines whether the given {@code String} is a valid printable ASCII string with a length
     * greater than 0 and not exceed {@link #MAX_LENGTH} characters.
     *
     * @param name the name to be validated.
     * @return whether the name is valid.
     */
    private static boolean isValidAndNotEmpty(String name) {
        return !name.isEmpty() && isValid(name);
    }

}

為其提供高度覆蓋的測(cè)試用例:

/** Unit tests for {@link Resource}. */
@RunWith(JUnit4.class)
public final class ResourceTest {
    @Rule
    public final ExpectedException thrown = ExpectedException.none();

    private static final Resource DEFAULT_RESOURCE =
        Resource.create(Collections.emptyMap());

    private Resource resource1;
    private Resource resource2;

    @Before
    public void setUp() {
        Map<String, String> labelMap1 = new HashMap<>();
        labelMap1.put("a", "1");
        labelMap1.put("b", "2");
        Map<String, String> labelMap2 = new HashMap<>();
        labelMap2.put("a", "1");
        labelMap2.put("b", "3");
        labelMap2.put("c", "4");
        resource1 = Resource.create(labelMap1);
        resource2 = Resource.create(labelMap2);
    }

    @Test
    public void create() {
        Map<String, String> labelMap = new HashMap<>();
        labelMap.put("a", "1");
        labelMap.put("b", "2");
        Resource resource = Resource.create(labelMap);
        assertThat(resource.getLabels()).isNotNull();
        assertThat(resource.getLabels().size()).isEqualTo(2);
        assertThat(resource.getLabels()).isEqualTo(labelMap);

        Resource resource1 = Resource.create(Collections.emptyMap());
        assertThat(resource1.getLabels()).isNotNull();
        assertThat(resource1.getLabels()).isEmpty();
    }

    @Test
    public void testResourceEquals() {
        Map<String, String> labelMap1 = new HashMap<>();
        labelMap1.put("a", "1");
        labelMap1.put("b", "2");
        Map<String, String> labelMap2 = new HashMap<>();
        labelMap2.put("a", "1");
        labelMap2.put("b", "3");
        labelMap2.put("c", "4");
        new EqualsTester()
            .addEqualityGroup(Resource.create(labelMap1), Resource.create(labelMap1))
            .addEqualityGroup(Resource.create(labelMap2))
            .testEquals();
    }

    @Test
    public void testMergeResources() {
        Map<String, String> expectedLabelMap = new HashMap<>();
        expectedLabelMap.put("a", "1");
        expectedLabelMap.put("b", "2");
        expectedLabelMap.put("c", "4");

        Resource resource = DEFAULT_RESOURCE.merge(resource1).merge(resource2);
        assertThat(resource.getLabels()).isEqualTo(expectedLabelMap);
    }

    @Test
    public void testMergeResources_Resource1_Null() {
        Map<String, String> expectedLabelMap = new HashMap<>();
        expectedLabelMap.put("a", "1");
        expectedLabelMap.put("b", "3");
        expectedLabelMap.put("c", "4");

        Resource resource = DEFAULT_RESOURCE.merge(null).merge(resource2);
        assertThat(resource.getLabels()).isEqualTo(expectedLabelMap);
    }

    @Test
    public void testMergeResources_Resource2_Null() {
        Map<String, String> expectedLabelMap = new HashMap<>();
        expectedLabelMap.put("a", "1");
        expectedLabelMap.put("b", "2");

        Resource resource = DEFAULT_RESOURCE.merge(resource1).merge(null);
        assertThat(resource.getLabels()).isEqualTo(expectedLabelMap);
    }

    @Test
    public void testCreateResources_ArgumentKey_Null() {
        Map<String, String> labelMap = new HashMap<>();
        labelMap.put("a", "1");
        labelMap.put(null, "2");
        thrown.expect(NullPointerException.class);
        Resource.create(labelMap);
    }

    @Test
    public void testCreateResources_ArgumentKey_Unprintable() {
        Map<String, String> labelMap = new HashMap<>();
        labelMap.put("a", "1");
        labelMap.put("\2b\3", "2");
        thrown.expect(IllegalArgumentException.class);
        Resource.create(labelMap);
    }

}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市纱注,隨后出現(xiàn)的幾起案子步做,更是在濱河造成了極大的恐慌,老刑警劉巖奈附,帶你破解...
    沈念sama閱讀 219,539評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件全度,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡斥滤,警方通過查閱死者的電腦和手機(jī)将鸵,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,594評(píng)論 3 396
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來佑颇,“玉大人顶掉,你說我怎么就攤上這事√粜兀” “怎么了痒筒?”我有些...
    開封第一講書人閱讀 165,871評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長茬贵。 經(jīng)常有香客問我簿透,道長,這世上最難降的妖魔是什么解藻? 我笑而不...
    開封第一講書人閱讀 58,963評(píng)論 1 295
  • 正文 為了忘掉前任老充,我火速辦了婚禮,結(jié)果婚禮上螟左,老公的妹妹穿的比我還像新娘啡浊。我一直安慰自己,他們只是感情好胶背,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,984評(píng)論 6 393
  • 文/花漫 我一把揭開白布巷嚣。 她就那樣靜靜地躺著,像睡著了一般钳吟。 火紅的嫁衣襯著肌膚如雪廷粒。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,763評(píng)論 1 307
  • 那天砸抛,我揣著相機(jī)與錄音评雌,去河邊找鬼树枫。 笑死直焙,一個(gè)胖子當(dāng)著我的面吹牛景东,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播奔誓,決...
    沈念sama閱讀 40,468評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼斤吐,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了厨喂?” 一聲冷哼從身側(cè)響起和措,我...
    開封第一講書人閱讀 39,357評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎蜕煌,沒想到半個(gè)月后派阱,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,850評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡斜纪,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,002評(píng)論 3 338
  • 正文 我和宋清朗相戀三年贫母,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片盒刚。...
    茶點(diǎn)故事閱讀 40,144評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡腺劣,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出因块,到底是詐尸還是另有隱情橘原,我是刑警寧澤,帶...
    沈念sama閱讀 35,823評(píng)論 5 346
  • 正文 年R本政府宣布涡上,位于F島的核電站趾断,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏吩愧。R本人自食惡果不足惜歼冰,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,483評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望耻警。 院中可真熱鬧隔嫡,春花似錦、人聲如沸甘穿。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,026評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽温兼。三九已至秸滴,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間募判,已是汗流浹背荡含。 一陣腳步聲響...
    開封第一講書人閱讀 33,150評(píng)論 1 272
  • 我被黑心中介騙來泰國打工咒唆, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人释液。 一個(gè)月前我還...
    沈念sama閱讀 48,415評(píng)論 3 373
  • 正文 我出身青樓全释,卻偏偏與公主長得像,于是被迫代替她去往敵國和親误债。 傳聞我的和親對(duì)象是個(gè)殘疾皇子浸船,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,092評(píng)論 2 355

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