是否应该对private的方法写UnitTest code

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/zxm317122667/article/details/82501386

最近在写UnitTest代码的时候遇到了点小问题,在解决过程当中有了点小心得,做一下记录。
完整代码在此处下载: https://download.csdn.net/download/zxm317122667/10652780

问题主要对于 private 方法的单元测试。

比如如下代码:有两个类,分别是FishRod
鱼Fish.java

package com.example.dannyjiang.testprivatedemo;

public class Fish {
    // 某一条鱼Fish的唯一标识
    public long id;
    // Fish的X坐标
    private int x;
    // Fish的Y坐标
    private int y;
    // Fish的大小,默认为100
    public int size = 100;

    public Fish() {
    }

    public int getX() {
        return x;
    }

    public void setX(int x) {
        this.x = x;
    }

    public int getY() {
        return y;
    }

    public void setY(int y) {
        this.y = y;
    }
}

鱼钩Rod.java

package com.example.dannyjiang.testprivatedemo;

public class Rod {
    // Rod的唯一标识
    public long id;
    // Rod的X坐标
    private int x;
    // Rod的Y坐标
    private int y;
    // Fish的大小,默认为100
    private int size = 100;

    public int getX() {
        return x;
    }

    public int getY() {
        return y;
    }

    public int getSize() {
        return size;
    }

    public void setX(int x) {
        this.x = x;
    }

    public void setY(int y) {
        this.y = y;
    }
}

以及一个用来判断一个Rod是否钓到了一条鱼Fish的封装类Controller.java

package com.example.dannyjiang.testprivatedemo;

import java.util.List;

public class Controller {

    /**
     * 传入一个List,判断在此List中,是否有与Rod匹配的Fish对象
     * @param fishList
     * @param rod
     */
    public boolean hasFishMatched(List<Fish> fishList, Rod rod) {
        for (Fish fish : fishList) {
            // 需要判断Fish是否与Rod重叠
            if (fishOverlapWithRod(fish, rod)) {
                System.out.println(String.format("fish %d has been matched", fish.id));
                return true;
            }
        }
        System.out.println("no fish found");
        return false;
    }

    /**
     * 判断Fish是否与某一个Rod有重叠
     * @param fish
     * @param rod
     * @return
     */
    private boolean fishOverlapWithRod(Fish fish, Rod rod) {

        return fish.getX() < rod.getX() + rod.getSize()
                && fish.getX() + fish.size > rod.getX()
                && fish.getY() < rod.getY() + rod.getSize()
                && fish.getY() + fish.size > rod.getY();
    }
}

可以看到在Controller类中提供了一个public的方法给外部调用。但是在这个方法中还调用了一个privatefishOverlapWithRod方法。这样就造成很难去给hasFishMatched写单元测试代码。在网上查过很多资料如何去给一个private的方法写单元测试。网上主要介绍了有几种框架,例如:PowerMOckitoJMockit、或者微软的MSTest。但是一次很偶然的机会看到了Practical Object Oriented Design in Ruby这本书的作者Sandi Metz写的一句话:

The solution to the problem of costly tests, however, Getting good value from tests requires
clarity of intention and knowing what, when, and how to test.

才发现如果想对一个private的方法去写UnitTest,则已经说明代码设计上是存在问题的。

问题主要是如下两点:

  1. 违反了类的单一职责原则(SRP)
  2. 这种写法属于Code Smell中的特性嫉妒(Feature envy)或者是数据簇

解决思路

就是使用代码重构中的 Extract Class 将判断FishRod是否重叠的代码抽象到Fish中。 修改后的代码如下:
Fish.java

package com.example.dannyjiang.testprivatedemo;

public class Fish {
    // 某一条鱼Fish的唯一标识
    public long id;
    // Fish的X坐标
    private int x;
    // Fish的Y坐标
    private int y;
    // Fish的大小,默认为100
    public int size = 100;

    public Fish() {
    }

    public boolean overlap(Rod rod) {
        if (rod == null) {
            throw new RuntimeException("rod is null");
        }
        return x < rod.getX() + rod.getSize()
                && x + size > rod.getX()
                && y < rod.getY() + rod.getSize()
                && y + size > rod.getY();
    }

    public int getX() {
        return x;
    }

    public void setX(int x) {
        this.x = x;
    }

    public int getY() {
        return y;
    }

    public void setY(int y) {
        this.y = y;
    }
}

可以看待在Fish.java中多了一个overlap方法专门用来判断它是否与传入的Rod重叠。 同时Rod.java不需要做其它的修改。这样修改后,Controller.java中只要改为如下即可:

package com.example.dannyjiang.testprivatedemo;

import java.util.List;

public class Controller {

    /**
     * 传入一个List,判断在此List中,是否有与Rod匹配的Fish对象
     * @param fishList
     * @param rod
     */
    public boolean hasFishMatched(List<Fish> fishList, Rod rod) {
        for (Fish fish : fishList) {
            // 需要判断Fish是否与Rod重叠
            if (fish.overlap(rod)) {
                System.out.println(String.format("fish %d has been matched", fish.id));
                return true;
            }
        }
        System.out.println("no fish found");
        return false;
    }
}

单元测试

最后分别对Controller.javaFish.java书写UnitTest代码即可实现功能代码的覆盖率,代码如下:
ControllerTest.java

package com.example.dannyjiang.testprivatedemo;

import org.junit.Test;

import java.util.ArrayList;
import java.util.List;

import static org.junit.Assert.*;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

public class ControllerTest {

    @Test
    public void no_fish_match1() {
        Controller controller = new Controller();

        List<Fish> fishList = new ArrayList<>();
        Fish fish1 = mock(Fish.class);
        Fish fish2 = mock(Fish.class);
        Fish fish3 = mock(Fish.class);
        fishList.add(fish1);
        fishList.add(fish2);
        fishList.add(fish3);

        Rod rod = mock(Rod.class);

        when(fish1.overlap(rod)).thenReturn(false);
        when(fish2.overlap(rod)).thenReturn(false);
        when(fish3.overlap(rod)).thenReturn(false);

        assertFalse(controller.hasFishMatched(fishList, rod));
    }

    @Test
    public void has_fish_match1() {
        Controller controller = new Controller();

        List<Fish> fishList = new ArrayList<>();
        Fish fish1 = mock(Fish.class);
        Fish fish2 = mock(Fish.class);
        Fish fish3 = mock(Fish.class);
        fishList.add(fish1);
        fishList.add(fish2);
        fishList.add(fish3);

        Rod rod = mock(Rod.class);

        when(fish1.overlap(rod)).thenReturn(false);
        when(fish2.overlap(rod)).thenReturn(true);
        when(fish3.overlap(rod)).thenReturn(false);

        assertFalse(controller.hasFishMatched(fishList, rod));
    }
}

FishTest.java

package com.example.dannyjiang.testprivatedemo;

import org.junit.Before;
import org.junit.Test;

import static org.junit.Assert.*;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

public class FishTest {

    private Fish fish = null;

    @Before
    public void setUp() {
        fish = new Fish();
        fish.id = 1;
        fish.setX(0);
        fish.setY(0);
        fish.size = 100;
    }

    @Test(expected = RuntimeException.class)
    public void process_fish_with_null() {
        fish.overlap(null);
    }

    @Test
    public void process_fish_not_overlapped() {
        Rod mockRod = mock(Rod.class);
        when(mockRod.getX()).thenReturn(101);
        when(mockRod.getY()).thenReturn(101);
        when(mockRod.getSize()).thenReturn(100);

        assertFalse(fish.overlap(mockRod));
    }

    @Test
    public void process_fish_overlapped() {
        Rod mockRod = mock(Rod.class);
        when(mockRod.getX()).thenReturn(50);
        when(mockRod.getY()).thenReturn(50);
        when(mockRod.getSize()).thenReturn(100);

        assertTrue(fish.overlap(mockRod));
    }

}

猜你喜欢

转载自blog.csdn.net/zxm317122667/article/details/82501386
今日推荐