关于JAVA后端的单元测试

1. 单元测试核心原则

  • 自动化:单元测试应该是全自动执行的,并且非交互式的。利用断言Assert进行结果验证。
  • 独立性:保持单元测试的独立性。为了保证单元测试稳定可靠且便于维护,单元测试用例之间决不能互相调用,也不能依赖执行的先后次序。 单测不负责检查跨类或者跨系统的交互逻辑,那是集成测试的领域。
  • 可重复:单元测试是可以重复执行的,不能受到外界环境的影响。如果单测对外部环境(网络、服务、中间件等)有依赖,容易导致持续集成机制的不可用。
  • 全面性:除了正确的输入得到预期的结果,还需要强制错误信息输入得到预期的结果,为了系统的鲁棒性,应加入边界值测试,包括循环边界、特殊取值、特殊时间点、数据顺序等。
  • 细粒度:保证测试粒度足够小,有助于精确定位问题。单测粒度至多是类级别,一般是方法级别。单测不负责检查跨类或者跨系统的交互逻辑,那是集成测试的领域。

2. 测试框架

JUnit和TestNG是最受欢迎的两个单元测试框架

JUnit

默认情况下,JUnit的运行顺序无规律

JUnit3:

//JUnit3的测试类必须继承TestCase
public class JUnit3Test extends TestCase {
    //JUnit3的测试方法必须test开头
    public void testName() {
        
    }
}

JUnit4;

//JUnit使用注解来运行,无需继承TestCase,也不需要测试驱动以test开头
public class JUnit4Test {
    @Test
    public void testName() {

    }
}

Test设置

  • JUnit:@BeforeClass和**@AfterClass的方法需要加*static***

    常用:

    • @BeforeClass:在所有测试方法前执行一次,一般在其中写上整体初始化的代码。
    • @AfterClass:在所有测试方法后执行一次,一般在其中写上销毁和释放资源的代码。
    • @Before:在每个测试方法前执行,有多少个测试方法就会运行多少次。
    • @After:在每个测试方法后执行,有多少个测试方法就会运行多少次。
    • @Test:具体的测试方法
    • @Ignore:执行测试时将忽略掉此方法,如果用于修饰类,则忽略整个类。
    • @Rule:定义一些规则,如超时时间,异常捕获

    不常用:

    • @Test(timeout = 1000):指明要被测试的方法(测试方法执行超过1000毫秒后算超时,测试将失败)。
    • @Test(expected = Exception.class):测试方法期望得到的异常类,如果方法执行没有抛出指定的异常,则测试失败。
    • @FixMethodOrder:可以指定按方法名的升序或是降序排序
    • @RunWith:指定用什么方式策略去运行这些测试集、类、方法。
    • @ActiveProfiles(“xxx”):在测试的时候启用某些Profile的Bean。
  • TestNG:

    • 方法级别:@BeforeMethod:测试方法运行前执行的方法注解

      ​ @AfterMethod:测试方法运行后执行的方法注解

    • 类级别:@BeforeClass

      ​ @AfterClass

    • 套件:@BeforeSuite、@AfterSuite

    • 组级别:@BeforeGroup、@AfterGroup

    • @Test

      • alwaysRun : 如果=true,表示即使该测试方法所依赖的前置测试有失败的情况,也要执行
      • dataProvider : 选定传入参数的构造器。(@DataProvider注解将在后面章节介绍)
      • dataProviderClass : 确定参数构造器的Class类。(参数构造器首先会在当前测试类里面查找,如果参数构造器不在当前测试类定义,那么必须使用该属性来执行它所在的Class类)
      • dependsOnGroups : 确定依赖的前置测试组别。
      • dependsOnMethods : 确定依赖的前置测试方法。
      • description : 测试方法描述信息。(建议为每个测试方法添加有意义的描述信息,这将会在最后的报告中展示出来)
      • enabled : 默认为true,如果指定为false,表示不执行该测试方法。
      • expectedExceptions : 指定期待测试方法抛出的异常,多个异常以逗号(,)隔开。
      • groups : 指定该测试方法所属的组,可以指定多个组,以逗号隔开。组测试的用法将在后面文章单独介绍。
      • invocationCount : 指定测试方法需要被调用的次数。
      • invocationTimeOut: 每一次调用的超时时间,如果invocationCount没有指定,该参数会被忽略。应用场景可以为测试获取数据库连接,超时就认定为失败。单位是毫秒。
      • priority : 指定测试方法的优先级,数值越低,优先级越高,将会优先与其他数值高的测试方法被调用。(注意是针对一个测试类的优先级)
      • timeout : 指定整个测试方法的超时时间。单位是毫秒
    • 多线程:threadPoolSize = 3,invocationCount = 6,timeOut = 1000

停用测试

  • JUnit:@Ignore
  • TestNG:@Test后面加入enable=false @Test(enable=false)

套件/分组测试

  • JUnit利用@RunWith、@SelectPackages、@SelectClasses注解来组合测试用例

    @RunWith(Suite.class)
    @Suite.SuiteClasses({
            JUnit3Test.class,
            JUnit4Test.class
    })
        public class JUnitTest {
    }
    
  • TestNG

    @Test(groups = {"group2","group1"})
    
    <test name="test-2">
            <groups>
                <run>
                    <include name="group2"/>
                    <include name="group1"/>
                </run>
            </groups>
            <packages>
                <package name="com.cxl.testng"/>
            </packages>
        </test>
    

参数化测试

  • JUnit

    @RunWith(value = Parameterized.class)
    public class JUnitTest {
    
        private int number;
    
        public JUnitTest06(int number) {
            this.number = number;
        }
    
        @Parameterized.Parameters
        public static Collection<Object[]> data() {
            Object[][] data = new Object[][]{{1},{2},{3},{4}} ;
            return Arrays.asList(data);
        }
    
        @Test
        public void pushTest() {
            System.out.println("Parameterized Number is : " + number);
        }
    }
    
  • TestNG

    @Parameters({"param1"})
        @Test
        public void paramterTest(String param1){
            System.out.println("\n---------------"+param1);
        }
    
    <parameter name="param1" value="http://127.0.0.1:4723/wd/hub" />
    <test name="testDemo1">
        <classes>
            <class name="com.cxl.testng.TestNG01"></class>
        </classes>
    </test>
    

@DataProvider 注解做参数化测试

  • TestNG:

    @Test(dataProvider = "userData")
    public void test(Class clazz, String[] str) {
        System.out.println(clazz + "-------------" + str[0]);
        System.out.println(clazz + "-------------" + str[1]);
    }
    
    @DataProvider(name = "userData")
    public Object[][] data() {
        Object[][] objects = new Object[][]{
                {Vector.class, new String[]{"java.util.Arrays", "java.util.List"}},
                {String.class, new String[]{"this is my str", "this is my pp"}},
                {Integer.class, new String[]{"123", "345"}},
                {Float.class, new String[]{"12.45f", "33.11f"}}};
        return objects;
    }
    

依赖测试

JUnit4 框架主要聚焦于测试的隔离,暂时还不支持这个特性。

TestNG使用dependOnMethods、dependsOnGroups 来实现了依赖测试的功能。

@Test
public  void  method1() {
   System.out.println("This is method 1");
}

@Test(dependsOnMethods={"method1"})
public  void  method2() {
    System.out.println("This is method 2");
}

如果method1()成功执行,那么method2()也将被执行,否则method2()将会被忽略。

    @Test(groups = { "init.1" })
    public void test1() {
    }
    @Test(groups = { "init.2" })
    public void test2() {
    }
    @Test(dependsOnGroups = { "init.*" })
    public void test3() { 
    }

并发测试

Junit单元测试不支持多线程测试,TestNg使用threadPoolSize用来指明线程池的大小。

public class TestNgThreadPoolSize {
    @Test(threadPoolSize = 3,invocationCount = 5)
    public void threadPool(){
        System.out.println("Thread ----------"+Thread.currentThread().getName());
    }
}

总结:

建议使用TestNG作为Java项目的核心单元测试框架,因为TestNG在参数化测试,依赖测试和套件测试(分组概念)方面更加突出。 TestNG用于高级测试和复杂集成测试。 它的灵活性对于大型测试套件尤其有用。 此外,TestNG还涵盖了整个核心的JUnit4功能。

3. Mock

在单元测试中模拟一个被调用方法,进而隔离测试环境,其实现原理是利用接口的多态性。

通常意义的mock指的就是mock server,模拟服务端返回的接口数据,用于前端开发

  • 为什么要用mock?
    1. 前后端开发速度不一致,前端开发速度快于后端,需要一个假的接口用于模拟后端返回。
    2. 项目需要用到第三方接口,如果第三方接口未开发好,或者第三方接口没有测试环境,为了保证进度,所以需要模拟接口用于调试。
@Mock
private IExecuteSqlManage iExecuteSqlManage;

@BeforeMethod
public void setUp() throws Exception {
    MockitoAnnotations.initMocks(this);
    ReflectionTestUtils.setField(AopTargetUtils.getTarget(espJfxxInfoFileToDb), "iExecuteSqlManage", iExecuteSqlManage);
}

@AfterMethod
public void tearDown() throws Exception {
    ReflectionTestUtils.setField(AopTargetUtils.getTarget(espJfxxInfoFileToDb), "iExecuteSqlManage", null);
}

4.PowerMock

1、类上加注解

@PowerMockIgnore("javax.management.*,org.apache.log4j.*")
@PrepareForTest({AcctFileProcessUtil.class, FileTool.class, GlobalConf.class}) //需要Mock的静态类


@PowerMockIgnore({"javax.xml.*", "org.xml.sax.*", "org.w3c.dom.*", "org.springframework.context.*", "org.apache.log4j.*"})

2、初始化

@ObjectFactory
public ITestObjectFactory getObjectFactory() {
    return new PowerMockObjectFactory();
}

3、自定义返回值

PowerMockito.mockStatic(GlobalConf.class);
PowerMockito.when(GlobalConf.getString(anyString(), any())).thenReturn("path_in");

4、检验次数

PowerMockito.verifyStatic(times(1));

5.断言

验证结果

@Test
void getPeopleById() {
    People people = peopleService.getPeopleById(1);
    //System.out.println(people);
    Assert.assertNotNull(people);
    Assert.assertEquals(people.getAge(), 11);
    Assert.assertEquals(people.getName(),"cxl");
}
try {
     agetAgentSeqImpl.getAgetAgentSeq(req);
} catch (Exception e) {
      Assert.assertEquals(e.getMessage(),"未取到代理商充值流水");
}
agetAgentSeqRes = agetAgentSeqImpl.getAgetAgentSeq(req);
Assert.assertEquals(agetAgentSeqRes.getXtableAgentSeq(), "null~0~-1~null~-1~1~0~-1~null~-1~0~20170301000000~null~0~0~0~-1~0~1~;");

验证方法是否被调用

@Test
public void testVerify()  {
    // 创建并配置 mock 对象
    MyClass test = Mockito.mock(MyClass.class);
    when(test.getUniqueId()).thenReturn(43);

    // 调用mock对象里面的方法并传入参数为12
    test.testing(12);
    test.getUniqueId();
    test.getUniqueId();

    // 查看在传入参数为12的时候方法是否被调用
    verify(test).testing(Matchers.eq(12));

    // 方法是否被调用两次
    verify(test, times(2)).getUniqueId();

    // 其他用来验证函数是否被调用的方法
    verify(mock, never()).someMethod("never called");
    verify(mock, atLeastOnce()).someMethod("called at least once");
    verify(mock, atLeast(2)).someMethod("called at least twice");
    verify(mock, times(5)).someMethod("called five times");
    verify(mock, atMost(3)).someMethod("called at most 3 times");
}

断言错误

@Test(expectedExceptions = com.newland.common.NLException.class)
    public void queryUserUnionPayUserIdMinusOne() throws Exception {
        try {
            qryUserUnionPayImpl.qryUserUnionPay(getReq("-1"));
        } catch (NLException e) {
            //断言结果是否与预期相同
            Assert.assertEquals(e.getResCode(), CloudErrorCode.ERR_PAKERR);
            Assert.assertEquals(e.getMessage(), "帐户userid节点没找到或值不对");
            throw e;
        }
    }
  • 0
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值