• 软件测试技术之单元测试—工程师 Style 的测试方法(2)


    怎么写单元测试

    JUnit简介

    基本上每种语言和框架都有不错的单元测试框架和工具,例如 Java的 JUnit、Scala 的 ScalaTest、Python 的 unittest、JavaScript 的 Jest 等。上面的例子都是基于 JUnit 的,我们下面就简单介绍下 JUnit。

    JUnit 里面每个 @Test 注解的方法,就是一个测试。@Ignore 可以忽略一个测试。@Before、@BeforeClass、@After、@AfterClass 可以在测试执行前后插入一些通用的操作,比如初始化和资源释放等等。

    除了 assertEquals,JUnit 也支持不少其他的 assert 方法。例如 assertNull、assertArrayEquals、assertThrows、assertTimeout 等。另外也可以用第三方的 assert 库比如 Spring 的 Assert 或者 AssertJ。

    除了可以测试普通的代码逻辑,JUnit 也可以进行异常测试和时间测试。异常测试是测试某段代码必须抛指定的异常,时间测试则是测试代码执行时间在一定范围内。

    也可以对测试进行分组。例如可以分成 contractTest 、mockTest 和 unitTest,通过参数指定执行某个分组的测试。

    这里就不做过多介绍了,想了解更多 JUnit 的可以去看 极客学院的 JUnit 教程 等资料。其他的单元测试框架,基本功能都是大同小异。

    使用测试 Double

    狭义的单元测试,我们是只测试单元本身。即使我们写的是广义的单元测试,它依然可能依赖其他模块,比如其他类的方法、第三方服务调用或者数据库查询等等,造成我们无法很方便的测试被测系统或模块。这时我们就需要使用测试 Double 了。

    如果细究的话,测试 Double 分成好多种,比如什么 Dummies、Fakes 等等。但我认为我们只要弄清两类就可以了,也就是 Stub 和 Mock。

    Stub

    Stub 指那些包含了预定义好的数据并且在测试时返回给调用者的对象。Stub 常被用于我们不希望返回真实数据或者造成其他副作用的场景。

    我们契约测试生成的、可以通过 spring cloud stubrunner 运行的 Stub Jar 就是一个 Stub。我们可以让 Stub 返回预设好的假数据,然后在单元测试里就可以依赖这些数据,对代码进行测试。例如,我们可以让用户查询 Stub 根据参数里的用户 ID 返回认证用户和未认证用户,然后我们就可以测试调用方在这两种情况下的处理逻辑了。

    当然,Stub 也可以不是远程服务,而是另外一个类。所以我们经常说要针对接口编程,因为这样我们就可以很容易的创建一个接口的 Stub 实现,从而替换具体的类。

    public class StubNameService implement NameService {

    public String get(String userId) {

    return ““Mock user name””;

    }

    }

    public class UserServiceTest {

    // UserService 依赖 NameService,会调用其 get 方法

    @Inject

    private UserService userService;

    @Test

    public void whenUserIdIsProvided_thenRetrievedNameIsCorrect() {

    userService.setNameService(new StubNameService());

    String testName = userService.getUserName(““SomeId””);

    Assert.assertEquals(““Mock user name””, testName);

    }

    }

    不过这样要实现很多 Stub 也是很麻烦的,现在我们已经不需要自己创建 Stub 了,因为有了各种 Mock 工具。

    Mock

    Mocks 指那些可以记录它们的调用信息的对象,在测试断言中我们可以验证 Mocks 被进行了符合期望的调用。

    Mock 和 Stub 的区别在于,Stub 只是提供一些数据,它并不进行验证,或者只是基于状态做一些验证;而 Mock 除了可以做 Stub 的事情,也可以基于调用行为进行验证。比如说,Mock 可以验证 Mock 接口被调用了不多不少正好两次,并且调用的参数是期望的数值。

    Java 里最常用的 Mock 工具就是 Mockito 了。我们来看一个简单的例子,下面的 UserService 依赖 NameService。当我们测试 UserService 的时候,我们希望隔离 NameService,那么就可以创建一个 Mock 的 NameService 注入到 UserService 中(在 Spring 里只需要用 @Mock 和 @InjectMocks 两个注解就可以完成了)

    public class UserServiceTest {

    @InjectMocks

    private UserService userService;

    @Mock

    private NameService nameService;

    @Test

    public void whenUserIdIsProvided_thenRetrievedNameIsCorrect() {

    Mockito.when(nameService.getUserName(““SomeId””)).thenReturn(““Mock user name””);

    String testName = userService.getUserName(““SomeId””);

    Assert.assertEquals(““Mock user name””, testName);

    Mockito.verify(nameService).getUserName(““SomeId””);

    }

    }

    注意上面最后一行,是验证 nameService 的 getUserName 被调用,并且参数为 ““SomeId””。

    契约测试

    契约测试会给每个服务生成一个 Stub,可以用于调用方的单元/集成测试。例如,我们需要测试预约服务的预约操作,而预约操作会调用用户服务,去验证用户的一些基本信息,比如医生是否认证等。

    所以,我们可以通过传入不同的用户 ID,让契约 Stub 返回不同状态的用户数据,从而验证不同的处理流程。例如,正常的预约流程的测试用例可能是这样的。

    @RunWith(SpringJUnit4ClassRunner.class)

    @SpringBootTest@AutoConfigureStubRunner(repositoryRoot=““http://””,

    ids = {"“com.xingren.service:user-client-stubs:1.0.0:stubs:6565"”})public class BookingTest {

    // BookingService 会调用用户服务,获取医生认证状态后进行不同的处理

    @Inject

    private BookingService bookingService;

    @Test

    public void testBooking() {

    BookingForm form = new BookingForm(

    1,// doctorId

    1// scheduleId

    1001);// patientId

    BookVO res = bookingService.book(form);

    assertTrue(res.id > 0);

    assertTrue(res.payStatus == PayStatus.UN_PAY);

    }

    }

    注意上面的 AutoConfigureStubRunner 注解就是设置并启动了用户服务 Stub,当然在测试的时候,我们需要把服务调用接口的 baseUrl 设置为http://localhost:6565。关于契约测试的更多内容,请参考微服务环境下的集成测试探索一文。

    TDD

    简单说下 Test Driven Development,也就是 TDD。左耳朵耗子就写了一篇TDD并不是看上去的那么美,我就直接引用其介绍了。

    其开发过程是从功能需求的test case开始,先添加一个test case,然后运行所有的test case看看有没有问题,再实现test case所要测试的功能,然后再运行test case,查看是否有case失败,然后重构代码,再重复以上步骤。

    其实严格的 TDD 流程实用性并不高,左耳朵耗子本身也是持批判态度。但是对于接口定义比较明确的模块,先写单元测试再写实现代码还是有很大好处的。因为目标清晰,而且可以立刻得到反馈。

    文章来源:网络 版权归原作者所有

    上文内容不用于商业目的,如涉及知识产权问题,请权利人联系小编,我们将立即处理

  • 相关阅读:
    Three 之 three.js (webgl)鼠标/手指通过射线移动物体的简单整理封装
    2023.11.6 Spring 使用注解存储 Bean 对象
    【DevOps】Git 图文详解(八):后悔药 - 撤销变更
    R: 阿尔法α多样性计算和箱图制作,以及差异分析
    【HTTP】GET 和 POST 的区别
    OpenGL 褐色
    redis基础
    雅思口语 23九月换题季最新考题答案
    [springmvc学习]8、JSR 303验证及其国际化
    简化后端:一篇带你走进云开发及小程序云开发的世界
  • 原文地址:https://blog.csdn.net/xuezhangmen/article/details/126360500