提高單元測(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)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容