第二节:测试数据表

测试

由于我们只是增加了一个接口,而接口的实现是由SpringMVC完成的,所以原则上,我们并不需要对其中的方法的正确与否进行测试。我们要做的仅仅是,调用这个接口进行某一个操作,是否可以得到我们的预期结果。如果某一个操作测试成功了,我们就认为整体的接口便是测试通过的。

package com.mengyunzhi.repository;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

/**
 * Created by panjie on 17/4/6.
 */
// 使用以下两个注解来说明:本测试类基于SpringBoot。(必须)
@RunWith(SpringRunner.class) @SpringBootTest
public class KlassRepositoryTest {
    // @Autowired注解:自动加载Spring为我们自动实例化的实现了KlassRepository接口的对象
    @Autowired
    private KlassRepository klassRepository;

    // @Before注解:在执行单元测试方法 前 执行。
    @Before
    public void before() {
    }

    // @After:在执行单元测试方法 后 执行。
    @After
    public void after() {
    }

    @Test
    public void test() {
        
    }
}

请思考:我们在此使用到了KlassRepository,但为什么不需要import呢?

班级中不含教师

    @Test
    public void test() {
        Klass klass = new Klass();
        klass.setName("网络一班");
        assertThat(klassRepository.save(klass)).isNotNull();
    }

[注意] assertThat包可能需要手动引用。

测试结果: /SpringMVC/assets/image/chapter3/9.png

/SpringMVC/assets/image/chapter3/10.png

乱码

数据表中,出现了乱码,这是由于我们虽然在创建数据库时,指定了数据库的默认编码为utf-8,但却把这项信息告知SpringMVC,为此,我们在配置文件中,将字符编码类型写入。

# 指定连接的类型为mysql 连接的地址为:localhost 端口为3306 ,数据为springmvc, 编码类型utf-8
spring.datasource.url=jdbc:mysql://localhost:3306/springmvc?characterEncoding=UTF-8

配置后,当我们再次进行测试时,发现乱码的问题已经被成功解决了。

班级中存在教师

    @Test
    public void addDataWithTeacher() {
        Teacher teacher = new Teacher("zhangsan",
                "zhangsan@yunzhiclub.com",
                "scse of hebut",
                false);
        Klass klass = new Klass(teacher, "一一班");
        klassRepository.save(klass);
    }

测试,我们将得到以下异常。

org.springframework.dao.InvalidDataAccessApiUsageException: org.hibernate.TransientPropertyValueException: 
    object references an unsaved transient instance - save the transient instance before flushing : 
        com.mengyunzhi.repository.Klass.teacher -> com.mengyunzhi.repository.Teacher; nested exception is java.lang.IllegalStateException:
            org.hibernate.TransientPropertyValueException: 
                object references an unsaved transient instance - save the transient instance before flushing : 
                    com.mengyunzhi.repository.Klass.teacher -> com.mengyunzhi.repository.Teacher

我们将后数的第2句进行翻译后,大概是说:对象引用了一个没有被保存的临时实例 -(解决方法) 在进行数据持久化(这里指save操作)这前,将这个临时实例进行保存。

我们大概分析一下,也是这样。我们虽然新例化了一个teacher, 但我们进行klass的数据存储时,需要用到teacher里的id字段,然后再进行存储。我们暂时把这个流程想像成。

/SpringMVC/assets/image/chapter3/11.png

按上述流程图,当执行到第3步,获取教师实体时,发现返回值为null,随后抛出了上述异常。实际上是这样吗?我们再测试:

    @Test
    public void addDataWithTeacher() {
        Teacher teacher = new Teacher("zhangsan",
                "zhangsan@yunzhiclub.com",
                "scse of hebut",
                false);
        teacher.setId(1000L);
        Klass klass = new Klass(teacher, "一一班");
        klassRepository.save(klass);
    }

此时将得到如下提示:

2017-04-13 11:41:43.029  WARN 9005 --- [           main] o.h.engine.jdbc.spi.SqlExceptionHelper   : SQL Error: 1452, SQLState: 23000
2017-04-13 11:41:43.030 ERROR 9005 --- [           main] o.h.engine.jdbc.spi.SqlExceptionHelper   : Cannot add or update a child row: a foreign key constraint fails (`springmvc`.`klass`, CONSTRAINT `FK2deq3vjpqt9i2282hhgrhc708` FOREIGN KEY (`teacher_id`) REFERENCES `teacher` (`id`))

org.springframework.dao.DataIntegrityViolationException: could not execute statement; SQL [n/a]; constraint [null]; nested exception is org.hibernate.exception.ConstraintViolationException: could not execute statement

是样代码的提示告诉我们,事实上SpringMVC不仅仅是获取了教师id的值,还会在进行数据插入时,去较验这个外键值对应的数据是否存在,如果存在,则会报错。

我们看异常的类型 – SqlExceptionHelper,大概可以猜出其是由SqlServer抛出的。对于现阶段的我们,完全不必理会是SpringMVC还是Hibernate或是MySql抛出的这个异常,我们只需要知道,在进行数据插入以前,如果加入了关联的实体,那么,这个实体在数据表中必须是真实存在的,否则将会抛出一个异常。

为了说明,本测试就是要抛出异常的,我们对其方法进行如下注解:

    @Test(expected = DataIntegrityViolationException.class)
    public void addDataWithTeacher() {
        Teacher teacher = new Teacher("zhangsan",
                "zhangsan@yunzhiclub.com",
                "scse of hebut",
                false);
        teacher.setId(1000L);
        Klass klass = new Klass(teacher, "一一班");
        klassRepository.save(klass);
    }

请思索:异常类型为什么是DataIntegrityViolationException

最后,我们再增加一个无异常的测试。

    @Autowired
    private TeacherRepository teacherRepository;
    @Test
    public void addKlassWithExistTeacher() {
        Teacher teacher = new Teacher("zhangsan",
                "zhangsan@yunzhiclub.com",
                "scse of hebut",
                false);
        teacherRepository.save(teacher);
        Klass klass = new Klass(teacher, "一一班");
        assertThat(klassRepository.save(klass)).isNotNull();
    }

测试测试类: /SpringMVC/assets/image/chapter3/12.png

关联删除

在设定外键约束后,不仅在数据添加时会进行验证,在执行相关的删除操作时,同样会进行相关较验。在上面的测试类执行后,不出意外的,你将在数据表中得到两个数据表,三条数据。我们测试下在未删除班级表中数据的前提下,直接删除教师数据,看会报什么异常。

/SpringMVC/assets/image/chapter3/1.gif

正如我们期待的一样,Navicat在执行数据删除时,发生了foreign key constraint fails(外键约束失败)异常 – Cannot delete or update a parent row: a foreign key constraint fails (springmvc.klass, CONSTRAINT FK2deq3vjpqt9i2282hhgrhc708 FOREIGN KEY (teacher_id) REFERENCES teacher (id))

我们使用单元测试的方法,同样能测试出关联删除异常的错误。

    @Test
    public void relationDeleteException() {
        Teacher teacher = new Teacher("zhangsan",
                "zhangsan@yunzhiclub.com",
                "scse of hebut",
                false);
        teacherRepository.save(teacher);
        Klass klass = new Klass(teacher, "一一班");
        klassRepository.save(klass);
        teacherRepository.delete(teacher);
    }
}

控制台:

Hibernate: delete from teacher where id=?
2017-04-13 13:45:37.662  WARN 11316 --- [           main] o.h.engine.jdbc.spi.SqlExceptionHelper   : SQL Error: 1451, SQLState: 23000
2017-04-13 13:45:37.662 ERROR 11316 --- [           main] o.h.engine.jdbc.spi.SqlExceptionHelper   : Cannot delete or update a parent row: a foreign key constraint fails (`springmvc`.`klass`, CONSTRAINT `FK2deq3vjpqt9i2282hhgrhc708` FOREIGN KEY (`teacher_id`) REFERENCES `teacher` (`id`))
2017-04-13 13:45:37.664  INFO 11316 --- [           main] o.h.e.j.b.internal.AbstractBatchImpl     : HHH000010: On release of batch it still contained JDBC statements

org.springframework.dao.DataIntegrityViolationException: could not execute statement; SQL [n/a]; constraint [null]; nested exception is org.hibernate.exception.ConstraintViolationException: could not execute statement

我们看到,控制台不仅打印出了MySql的报错信息,还抛出了一个类型为org.springframework.dao.DataIntegrityViolationException的异常。

加入异常断言,完善测试代码:

    @Test(expected = DataIntegrityViolationException.class)
    public void relationDeleteException() {
        Teacher teacher = new Teacher("zhangsan",
                "zhangsan@yunzhiclub.com",
                "scse of hebut",
                false);
        teacherRepository.save(teacher);
        Klass klass = new Klass(teacher, "一一班");
        klassRepository.save(klass);
        teacherRepository.delete(teacher);
    }

最终执行单元测试类测试如下:

/SpringMVC/assets/image/chapter3/13.png

请思索:单元测试前,数据表中没有任何数据;我们经过单元测试后,观察数据表发现在数据表中产生了4条数据。这可能会导致什么问题?如何规避这种问题的产生。

参考: https://docs.jboss.org/hibernate/orm/current/userguide/html_single/Hibernate_User_Guide.htm