实验中用到Hamcrest的一些总结

Junit

JUnit框架用一组assert方法封装了一些常用的断言。这些assert方法可以帮我们简化单元测试的编写。这样的话,Junit就可以根据这些断言是否抛出 AssertionFailedError 错误来判断测试用例的执行结果。

Hamcrest

使用过Junit 的应该有过体验:在实际开发中,一些基本的断言,如eqaul,null,true它们的可读性并不是很好。而且很多时候我们要比较对象、集合、Map等数据结构。这样我们要么进行大段的字段获取再断言。或者干脆自己编写表达式并断言其结果。
JUnit4.4引入了Hamcrest框架,Hamcest提供了一套匹配符Matcher,这些匹配符更接近自然语言,可读性高,更加灵活。

Hamcrest 提供了大量被称为“匹配器”的方法。其中每个匹配器都设计用于执行特定的比较操作。Hamcrest的可扩展性很好,让你能够创建自定义的匹配器。最重要的是,JUnit也包含了Hamcrest的核心,提供了对Hamcrest的原生支持,可以直接使用Hamcrest。当然要使用功能齐备的Hamcrest,还是要引入对它的依赖。

看个对比例子,前者使用Junit的 断言,后者使用 Hamcrest的断言。

    @Test
    public void test_with_junit_assert() {
        int expected = 51;
        int actual = 51;
 
        assertEquals("failure - They are not same!", expected, actual);
    }
 
    @Test
    public void test_with_hamcrest_assertThat() {
        int expected = 51;
        int actual = 51;
 
        assertThat("failure - They are not same!", actual, equalTo(expected));
    }

个人感觉有两个明显的区别:

  1. 参数顺序。两者的expected 和 actual 前后顺序是相反的。
  2. Hamcrest 几乎总是直接使用对象。它的语法更符合函数式编程的风格。
    这点很好理解了,Junit 总是获取值后再比较,因为比较的是简单的值,因此被比较的放在前面更符合习惯。
    而Hamcrest 是直接对测试结果的对象进行一些更复杂的匹配。

支持语言

Hamcrest 支持以下几种语言,详情见http://hamcrest.org/
Java
Python
Ruby
Objective-C
PHP
Erlang
Swift

Hamcrest匹配器

Hamcrest 提供了很强大的一些api 供我们进行测试断言。

核心:
    anything - 总是匹配,如果你不关心测试下的对象是什么是有用的
    describedAs - 添加一个定制的失败表述装饰器
    is - 改进可读性装饰器 - 见下 “Sugar”
逻辑:
    allOf - 如果所有匹配器都匹配才匹配,像Java里的&&
    anyOf - 如果任何匹配器匹配就匹配,像Java里的||
    not - 如果包装的匹配器不匹配器时匹配,反之亦然
对象:
    equalTo - 测试对象相等使用Object.equals方法
    hasToString - 测试Object.toString方法
    instanceOf, isCompatibleType - 测试类型
    notNullValue, nullValue - 测试null
    sameInstance - 测试对象实例
Beans:
    hasProperty - 测试JavaBeans属性
集合:
    array - 测试一个数组元素test an array’s elements against an array of matchers
    hasEntry, hasKey, hasValue - 测试一个Map包含一个实体,键或者值
    hasItem, hasItems - 测试一个集合包含一个元素
    hasItemInArray - 测试一个数组包含一个元素
数字:
    closeTo - 测试浮点值接近给定的值
    greaterThan, greaterThanOrEqualTo, lessThan, lessThanOrEqualTo - 测试次序
文本:
    equalToIgnoringCase - 测试字符串相等忽略大小写
    equalToIgnoringWhiteSpace - 测试字符串忽略空白
    containsString, endsWith, startsWith - 测试字符串匹配

这些API 几乎覆盖了我们测试断言的所有情况。再提供良好阅读性的情况下,减少了一些取值、循环、类型判断等代码的编写。

不足

这里说的不足,只是本人使用过程中遇到的问题。如果大牛发现Hamcrest 现有的api 支持,欢迎指正。

hasProperty

先看一个复杂的层级结构。

public class MyPerson {
    private Address address;
    private String name;
    private String fullName;
 
    // ...
}

其中 Address 类如下:

public class Address {
 
    private String companyAddress;
    private String personalAddress;
 
    // ...
}

这里我们测试一个person对象,有 companyAddress 属性。

    @Test
    public void test_with_complex_property() {
        MyPerson p = new MyPerson();
        Address address = new Address();
        address.setCompanyAddress("");
        p.setAddress(address);
        assertThat("failure has no address property !", p, hasProperty("address"));
 
        // would failed
        // assertThat("failure has no address.companyAddress !", p, hasProperty("address.companyAddress"));
    }

这个单元测试的第二个断言出错。也就是说断言 hasProperty 时,不能跨层。这点就没有 rest-assured 的API 用着那么方便了。

这里我想了个办法如下:

assertThat(“failure has no address property !”, p, hasProperty(“address”, hasProperty(“companyAddress”)));

进行了一次嵌套后,就可以满足我们的需要了。

集合的泛型

这里我们要处理集合,断言每个元素都有属性:

    @Test
    public void test_with_list_generics() {
        List persons = new ArrayList<MyPerson>();
        MyPerson p = new MyPerson();
        p.setName("KaKa");
        persons.add(p);
 
        MyPerson p2 = new MyPerson();
        p2.setName("Hust");
        persons.add(p2);
 
        // compile error
        // assertThat("failure has no address property !", persons, everyItem(hasProperty("address")));
        assertThat("failure has no address property !", (List<Object>)persons, everyItem(hasProperty("address")));
    }

这我就没法理解了。显然,被注释的那句断言直接编译失败!然后向上转型后,反而成功。

对象自身属性比较

还用上面的例子,每个 person对象 都有 name 和 fullName 两个属性,断言 fullName 是startwith name 属性。

    @Test
    public void test_with_list_compare_with_self() {
        // 加上 类型
        List persons = new ArrayList<MyPerson>();
        MyPerson p = new MyPerson();
        p.setName("KaKa");
        p.setFullName("KaKa Zhang");
        persons.add(p);
 
        MyPerson p2 = new MyPerson();
        p2.setName("Hust");
        p.setFullName("Hust Zhang");
        persons.add(p2);
 
        // 这里没法写, person对象 属性 startsWith 另一个属性
        assertThat((List<Object>)persons, everyItem(hasProperty("name", startsWith(""))));
    }

这时候只能循环来断言,某个属性startwWith 另一个属性了。

    @Test
    public void test_with_list_compare_with_self() {
        // 加上 类型
        List<MyPerson> persons = new ArrayList<MyPerson>();
        MyPerson p = new MyPerson();
        p.setName("KaKa");
        p.setFullName("KaKa Zhang");
        persons.add(p);
 
        MyPerson p2 = new MyPerson();
        p2.setName("Hust");
        p2.setFullName("Hust Zhang");
        persons.add(p2);
 
        // 还是用循环来做
        for (MyPerson person : persons) {
            assertThat(person.getFullName().startsWith(""), is(true));
        }
    }

上面的这几个时我遇到几个情况。如果大牛有更好的做法,欢迎指点。


作者:hustzw07
来源:CSDN
原文:https://blog.csdn.net/hustzw07/article/details/79145253

走马灯实验通常使用LED灯作为外部设备,需要通过相应的头文件来定义LED灯的寄存器结构和初始化数据结构。在头文件,通常包含了LED灯的寄存器地址、寄存器结构和初始化数据结构。 LED灯的初始化数据结构通常包含了LED灯的各种配置参数,比如时钟源、时钟分频、工作模式、断使能等。在程序,可以通过修改这些参数来控制LED灯的工作状态和功能。 以51单片机为例,如果需要使用LED灯,可以定义一个LED.h头文件,其包含了LED灯的寄存器地址、寄存器结构和初始化数据结构。比如可以定义一个LED初始化数据结构如下: ``` typedef struct { unsigned char mode; // 工作模式 unsigned char freq_div; // 时钟分频 unsigned char int_en; // 断使能 } LED_INIT_DATA; ``` 这个结构体定义了LED灯的初始化数据结构,其mode字段表示工作模式,freq_div字段表示时钟分频,int_en字段表示断使能。在程序,可以通过修改这些字段来控制LED灯的工作状态和功能。 比如可以使用以下代码来初始化LED灯: ``` LED_INIT_DATA led_init_data; led_init_data.mode = 0x01; // 手动模式 led_init_data.freq_div = 0x00; // 时钟源为Fosc/12 led_init_data.int_en = 0x00; // 禁止断 LED_Init(&led_init_data); // 初始化LED灯 ``` 在LED_Init函数,可以根据不同的参数设置来配置LED灯的各种寄存器,比如可以设置控制寄存器、数据寄存器、时钟源、时钟分频等。具体实现方法可以参考外设的数据手册或者硬件电路图来了解。 总之,外设的头文件的初始化数据结构定义了外设的各种配置参数,程序可以通过修改这些参数来控制外设的工作状态和功能。理解这些参数的含义和作用,是嵌入式系统开发的重要内容。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值