接触到junit是在大三的时候,学校有一门叫软件测试的课程,当时也认真学了一会,然后就没有然后了,以为平时程序能跑起来跟踪代码debug一下就是一个测试了,加上平时用的不多,也没有写测试的习惯。
现在回头复习体会,学习junit不是没有道理的。

什么是单元测试

单元测试是开发人员编写的一小段代码,用来检测一个有明确功能的小模块代码是否正确。

  • 通常拿来判断某个类和函数的行为
  • 白盒测试(开发人员了解代码内部逻辑的测试);对应的是黑盒测试,使用黑盒测试只对外提供输入输出,并不了解内部的原理。
  • 开发人员是最大受益者

单元测试的优点

  • 验证行为
    • 保证代码的正确性
    • 回归测试:添加新功能修改程序结构,不用担心破坏重要功能
    • 给重构带来保证
  • 设计行为
    • 测试驱动:从调用者的角度观察和思考问题,使代码设计成可测试,松耦合的
  • 文档行为
    • 描述代码的行为

单元测试的原则


实操例子

功能模块

比如说以下例子,处理一个字符串得出里面得值,再返回。比如:"10 + 20 - 5"得处理应该返回25

    public class Calculator {
        public int evaluate(String exp) {
            //对exp进行解析,执行运算 比如:"10 + 20 - 5"
            int result = ;
            return result;
        }
    }

1. 人肉测试

    public static void main(String[] args) {
        Calculator calculator = new Calculator();
        int result = calculator.evaluate("10+20-5");
        System.out.println(result);
    }

这是第一种方法,直接把运算整块写到代码种,手工运行结果是否如我所愿。这种做法显而易见,是需要执行main中所有的功能,这是在代码不多的情况下,那如果需要测试很多代码呢??

有的人说代码多就多呗,一样可以测试,那如果测试的代码中包含多个类呢,一个类需要调用其他的类,修改的类影响到其他的类,这种情况下所有符合测试数据的地方都要修改,也可能要手工运行一些调用类的main方法,乱的要命你丫不累死了?

也有人说既然代码测试那么麻烦,那你就别测了呗,公司自有测试的人。好了秀儿,你可以走了。

使用这种人肉测试方法,显然,是不可取的。

那怎么办呢,我们的前辈早已经想到了这种情况,能不能有个测试框架来运行所有的测试代码呢??答案自然是有的,testNG、junit等
现在我们在项目中常见的就是junit,我们使用gradle/maven时自带的jar包也是junit。那么接下来就介绍介绍Junit!

2. 测试框架使用

  • 2.1 我们在使用测试框架(这里指junit)前,先了解一下下下面两个东西,测试用例和注解:

    • 测试用例:我们需要测试的模块功能
    • 测试注解:@Test,用来标注测试用例,在不适用main方法的情况下执行测试用例
  • 2.2 测试代码路径
    src/test/java/xxxTest.java

  • 2.3.1 gogogogogo,动手写吧,别偷懒。ps:使用前你得导入junit jar包,balabalabalabalabala

    public class CalculatorTest {
    @Test
    public void testEvaluate1() {//测试用例
        Calculator calculator = new Calculator();
        int result = calculator.evaluate("10+20-5");
        Assert.assertEquals(25,result);
    }
    }

    这里出现的Assert.assertEquals(25,result);是啥东西呢?官方说法叫断言,下面大概讲讲参数的使用。
    就需要讲讲期待值和结果值这两个东西,对应的正是assertEquals的参数。如果代码执行的结果值符合你想象中正常的值,junit测试框架可以根据你给的值对结果判断,相等绿色~,不等就报红色。ps:如果感兴趣看看Assert,里面封装了大量测试用到的方法。
    Junit可以说是自动执行,自动判断的框架,不用你写main方法执行,给了期待结果就可以判断是否,不用你再对比结果。

  • 2.3.2 上面例子是测试代码少的时候,那么多代码又是怎样测得捏??

    对照上面的图片可以看到右边litespring.test包下y有AllTest.java类,其中代码对应左边,使用suiteclasses注解包含了若干个测试类作为一个套件来执行。比如包含v1AllTests.class/v2.../v3../v4..等等。
    那包含的测试类里面都有些什么呢?我们来看看v4AllTests.java,它把所在包的Test类包含了进来,前面的AllTests.java又包含了V4AllTests.java,所以在包根路径下的AllTests.java就把所有的测试用例都包含了起来。
    这样做有什么好处呢??
    比如说我们执行v4包下的单个test,点击运行,也可以运行v4包下的所有test,点击V4AllTests.java运行,也可以运行项目下所有的test,点击AllTests.java运行,这也是我们平时说到的粒度,也是我们设计模式中的组合模式。

3. 断言

断言:Assert,可以自动帮我们检查结果。
常用的几个断言:

Assert.assertEquals(expected,actual)//两个数值,变量,对象是否相等
Assert.assertTrue(condition);
Assert.assertNotNull(object);
Assert.assertArrayEquals(expecteds,actual);//两个数组是否相等

4. 异常Exception

条件不满足,抛出异常,那么对异常该怎么测试呢??

    @Test
    public void testEvaluateWrongExcpetion() {
        Calculator calculator = new Calculator();
        try {
            int result = calculator.evaluate("4/0");
        } catch (ArithmeticException e) {
            return;
        }
        Assert.fail();
    }
    //相当于上面
    @Test(expected = ArithmeticException.class)
    public void testEvaluateWrongException() {
        Calculator calculator = new Calculator();
        int result = calculator.evaluate("4/0");
    }

5. @Before,@After注解


这两个注解可以在每个测试用例调用前后进行调用

    @Before
    public void setUp() {
        //每个测试用例执行前都被调用一次
        System.out.println("每个测试用例执行前都被调用一次");
    }
    @After
    public void tearDown() {
        //每个测试用例执行后都被调用一次
        System.out.println("每个测试用例执行后都被调用一次");
    }

6. @BeforeClass,@AfterClass注解


在所有测试用例执行完前后进行调用

7. 使用mock对象


  • 例子
    public class URLParser {
    public void parse(HttpServletRequest request) {
        String name = request.getParameter("name");
        //继续业务逻辑
    }
    }

    上面的方法依赖tomcat实现,如果使用junit测试(不在tomcat中),开发人员写一个对接口HttpServletRequest方法,就不得不实现几十个无用的方法

这种情况就可以使用mmock方法,easyMock

8. 对遗留代码进行测试

  • 策略

  • 步骤
      1. 确定要测试的类和函数
      1. 解除依赖
      1. 编写测试用例
      1. 重构代码

9. junit单元测试的运行

10. 好的junit

书籍推荐:
《修改代码的艺术》