Java高级--Junit单元测试

Jnuit单元测试

针对最小的功能单元编写测试代码:Java中最小的单元是方法,java单元测试针对的是方法的测试

main()测试的缺点

  • 无法将测试的代码分离
  • 无法打印测试结果和期望值对比

因此需要给测试框架,进行单元测试

Junit

开源的Java语言测试框架,可以给出成功的测试和失败的测试,还可以生成测试报告,不仅可以统计测试的成功率,还可以统计测试的代码覆盖率。

使用

  1. 创建maven项目

    maven作用: a.规范目录结构 b.代码仓库

  2. 引入junit最新依赖

     <dependency>
          <groupId>org.junit.jupiter</groupId>
          <artifactId>junit-jupiter-api</artifactId>
          <version>5.7.0</version>
          <scope>test</scope>
    </dependency>
    <dependency>
          <groupId>org.junit.jupiter</groupId>
          <artifactId>junit-jupiter-params</artifactId>
          <version>5.6.2</version>
          <scope>test</scope>
    </dependency>
    

    在mvnrepository.com(mvn仓库)查找

    3.方法上加 @Test注解,表明此方法为测试方法

    常用Assertion中的断言方法

    • assertEquals :验证是否相等
    • assertTrue: 期待结果为true
    • assertFalse:期待结果为false
    • assertNotNull:期待结果非null
    • assertArrayEquals:期待结果为数组,并且每个元素的值均相等
    • ......
    public class Factorial {
        public static long fact(long n) {
            long r = 1;
            for (long i = 1; i <= n; i++) {
                r = r * i;
            }
            return r;
        }
    }
    //阶乘
    3!= 1*2*3
    

    测试

    public class FactorialTest {
        @Test
        void testFact() {
            assertEquals(1, Factorial.fact(1));
            assertEquals(2, Factorial.fact(2));
            assertEquals(6, Factorial.fact(3));
        }
    }
    

    4.实际方法中很多都不是静态方法,需要初始化,这是该怎么办呢?

    public class Calculator {
        private long n = 0;
    
        public long add(long x) {
            n = n + x;
            return n;
        }
    
        public long sub(long x) {
            n = n - x;
            return n;
        }
    }
    

    可以在每个测试方法前初始化,这样很麻烦

    JUnit提供了@BeforeEach @AfterEach,会在每个@Test前后运行,适用于大多数情况

    public class CalculatorTest {
    
        Calculator calculator;
    
        @BeforeEach
        public void setUp() {
            this.calculator = new Calculator();
        }
    
        @AfterEach
        public void tearDown() {
            this.calculator = null;
        }
    
        @Test
        void testAdd() {
            assertEquals(100, this.calculator.add(100));
            assertEquals(150, this.calculator.add(50));
            assertEquals(130, this.calculator.add(-20));
        }
    
        @Test
        void testSub() {
            assertEquals(-100, this.calculator.sub(100));
            assertEquals(-150, this.calculator.sub(50));
            assertEquals(-130, this.calculator.sub(-20));
        }
    }
    

    @BeforAll @AfterAll在所有@Test方法前后仅运行一次,只能标注在静态方法上。只有初始化资源特别耗时时才使用

    5.异常测试

    当我们想判断抛出异常的情况,JUnit提供了 assertThrows方法

    public class Factorial {
         public static long fact(long n) {
              if (n < 0) {
                throw new IllegalArgumentException();
            }
            long r = 1;
            for (long i = 1; i <= n; i++) {
                r = r * i;
            }
            return r;
        }
    }
    
    @Test
    void testNegative() {
        assertThrows(IllegalArgumentException.class, new Executable() {
            @Override
            public void execute() throws Throwable {
                Factorial.fact(-1);
            }
        });
    }
    

    第二个参数是一个函数式接口,可以使用 () ->{} 替代

    @Test
    void testNegative() {
        assertThrows(IllegalArgumentException.class, () -> {
            Factorial.fact(-1);
        });
    }
    

    6.条件测试

    跳过某些测试方法,会在结果显示跳过

    @Disabled
    @Test
    void testBug101() {
        // 这个测试不会运行
    }
    

    判断当前运行的操作系统

    public class Config {
        public String getConfigFile(String filename) {
            String os = System.getProperty("os.name").toLowerCase();//WINDOWS windows
            if (os.contains("win")) {
                return "C:\\" + filename;
            }
            if (os.contains("mac") || os.contains("linux") || os.contains("unix")) {
                return "/usr/local/" + filename;
            }
            throw new UnsupportedOperationException()
            ;
        }
    }
    
    @Test
    void testWindows() {
        assertEquals("C:\\test.ini", config.getConfigFile("test.ini"));
    }
    
    @Test
    void testLinuxAndMac() {
        assertEquals("/usr/local/test.cfg", config.getConfigFile("test.cfg"));
    }
    

    一个只能在windows上跑,领域给只能在Linux上跑,加上@EnableOnOs(OS.XXX)注解

    @Test
    @EnabledOnOs(OS.WINDOWS)
    void testWindows() {
        assertEquals("C:\\test.ini", config.getConfigFile("test.ini"));
    }
    
    @Test
    @EnabledOnOs({ OS.LINUX, OS.MAC })
    void testLinuxAndMac() {
        assertEquals("/usr/local/test.cfg", config.getConfigFile("test.cfg"));
    }
    

    只能在Java 9或更高版本执行的测试,可以加上@DisabledOnJre(JRE.JAVA_8):

    @Test
    @DisabledOnJre(JRE.JAVA_8)
    void testOnJava9OrAbove() {
        // TODO: this test is disabled on java 8
    }
    

    7.参数化测试

    如果想同时测一组数据,这时就要考虑给测试方法传入参数了

    @ParameterizedTest
    @ValueSource(ints = { 0, 1, 5, 100 })
    void testAbs(int x) {
        assertEquals(x, Math.abs(x));
    }
    
    @ParameterizedTest
    @ValueSource(ints = { -1, -5, -100 })
    void testAbsNegative(int x) {
        assertEquals(-x, Math.abs(x));
    }
    

    如果返回值比较复杂,例如把一个字符串首字母大写,其余字母小写

    public class StringUtils {
        public static String capitalize(String s) {
            if (s.length() == 0) {
                return s;
            }
            return Character.toUpperCase(s.charAt(0)) + s.substring(1).toLowerCase();
        }
    }
    
    @ParameterizedTest
    void testCapitalize(String input, String result) {
        assertEquals(result, StringUtils.capitalize(input));
    }
    

    测试要给出输入输出

    参入如何传入呢?有三种方式

    方式一: 使用@MethodSource 注解,可以编写要给同名静态方法

    @ParameterizedTest
    @MethodSource
    void testCapitalize(String input, String result) {
        assertEquals(result, StringUtils.capitalize(input));
    }
    
    static List<Arguments> testCapitalize() {
        List<Arguments> list = new ArrayList<>();
            list.add(  Arguments.arguments("abc","Abc"));
            list.add(Arguments.arguments("APP","App"));
            return list;
    }
    

    方式二:使用@CsvSource

    @ParameterizedTest
    @CsvSource({ "abc, Abc", "APPLE, Apple", "gooD, Good" })
    void testCapitalize(String input, String result) {
        assertEquals(result, StringUtils.capitalize(input));
    }
    

    方式三:数据量如果比较大,使用外部文件@CsvFileSource

    @ParameterizedTest
    @CsvFileSource(resources = { "/test-capitalize.csv" })
    void testCapitalizeUsingCsvFile(String input, String result) {
        assertEquals(result, StringUtils.capitalize(input));
    }
    

    JUnit只在classpath中查找指定的CSV文件,因此,test-capitalize.csv这个文件要放到test目录下

    pple, Apple
    HELLO, Hello
    JUnit, Junit
    reSource, Resource
    

    规范

    1. 单元测试的代码本身要非常简单,明了

    2. 每个单元测试应当相互独立,不依赖运行的顺序

    3. 测试时不仅要覆盖常用的测试用例,还要特别注意测试的边界条件,例如 0,null,“”等情况

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容