简体   繁体   English

当我已经有了集成测试时,我应该为 CRUD 操作编写单元测试吗?

[英]Should I write Unit-Tests for CRUD operations when I have already Integration-Tests?

In our recent project Sonar was complaining about a weak test coverage.在我们最近的项目中,Sonar 抱怨测试覆盖率低。 We noticed that it didn't consider integration tests by default.我们注意到它默认不考虑集成测试。 Beside the fact that you can configure Sonar, so it will consider them (JaCoCo plugin), we were discussing the question in our team if there really is the need to write Unit Tests, when you cover all your service and database layer with integration tests anyway.除了您可以配置 Sonar,因此它会考虑它们(JaCoCo 插件)这一事实之外,当您使用集成测试覆盖所有服务和数据库层时,我们还在我们的团队中讨论了是否真的需要编写单元测试的问题反正。

What I mean with integration tests is, that all our tests run against a dedicated Oracle instance of the same type we use in production.我对集成测试的意思是,我们所有的测试都针对我们在生产中使用的相同类型的专用 Oracle 实例运行。 We don't mock anything.我们不嘲笑任何东西。 If a service depends on another service, we use the real service.如果一个服务依赖于另一个服务,我们使用真正的服务。 Data we need before running a test, we construct through some factory classes that use our Services/Repositories (DAOs).在运行测试之前我们需要的数据,我们通过一些使用我们的服务/存储库 (DAO) 的工厂类来构建。

So from my point of view - writing integration tests for simple CRUD operations especially when using frameworks like Spring Data/Hibernate is not a big effort.所以从我的角度来看 - 为简单的 CRUD 操作编写集成测试,尤其是在使用 Spring Data/Hibernate 等框架时并不是一件大事。 It is sometimes even easier, because you don't think of what and how to mock.有时甚至更容易,因为您不考虑模拟什么以及如何模拟。

So why should I write Unit Tests for my CRUD operations that are less reliable as the Integration Tests I can write?那么为什么我应该为我的 CRUD 操作编写单元测试,而这些单元测试不如我可以编写的集成测试可靠?

The only point I see is that integration tests will take more time to run, the bigger the project gets.我看到的唯一一点是集成测试需要更多的时间来运行,项目越大。 So you don't want to run them all before check-in.所以你不想在登记前运行它们。 But I am not so sure if this is so bad, if you have a CI environment with Jenkins/Hudson that will do the job.但我不太确定这是否如此糟糕,如果你有一个 Jenkins/Hudson 的 CI 环境可以完成这项工作。

So - any opinions or suggestions are highly appreciated!所以 - 任何意见或建议都非常感谢!

If most of your services simply pass through to your daos, and your daos do little but invoke methods on Spring's HibernateTemplate or JdbcTemplate then you are correct that unit tests don't really prove anything that your integration tests already prove.如果您的大部分服务只是传递到您的 daos,而您的 daos 几乎没有做任何事情,只是调用 Spring 的HibernateTemplateJdbcTemplate上的方法,那么您是正确的,单元测试并没有真正证明您的集成测试已经证明的任何内容。 However, having unit tests in place are valuable for all the usual reasons.但是,出于所有常见原因,进行单元测试是有价值的。

Since unit tests only test single classes, run in memory with no disk or network access, and never really test multiple classes working together, they normally go like this:由于单元测试只测试单个类,在没有磁盘或网络访问的内存中运行,并且从来没有真正测试多个类一起工作,它们通常是这样的:

  • Service unit tests mock the daos.服务单元测试模拟 daos。
  • Dao unit tests mock the database driver (or spring template) or use an embedded database (super easy in Spring 3). Dao 单元测试模拟数据库驱动程序(或 spring 模板)或使用嵌入式数据库(在 Spring 3 中超级简单)。

To unit test the service that just passes through to the dao, you can mock like so:要对刚刚传递到 dao 的服务进行单元测试,您可以像这样模拟:

@Before
public void setUp() {
    service = new EventServiceImpl();
    dao = mock(EventDao.class);
    service.EventDao = dao;
}

@Test
public void creationDelegatesToDao() {
    service.createEvent(sampleEvent);
    verify(dao).createEvent(sampleEvent);
}

@Test(expected=EventExistsException.class)
public void creationPropagatesExistExceptions() {
    doThrow(new EventExistsException()).when(dao).createEvent(sampleEvent);
    service.createEvent(sampleEvent);
}

@Test
public void updatesDelegateToDao() {
    service.updateEvent(sampleEvent);
    verify(dao).updateEvent(sampleEvent);
}

@Test
public void findingDelgatesToDao() {
    when(dao.findEventById(7)).thenReturn(sampleEvent);
    assertThat(service.findEventById(7), equalTo(sampleEvent));

    service.findEvents("Alice", 1, 5);
    verify(dao).findEventsByName("Alice", 1, 5);

    service.findEvents(null, 10, 50);
    verify(dao).findAllEvents(10, 50);
}

@Test
public void deletionDelegatesToDao() {
    service.deleteEvent(sampleEvent);
    verify(dao).deleteEvent(sampleEvent);
}

But is this really a good idea?但这真的是个好主意吗? These Mockito assertions are asserting that a dao method got called, not that it did what was expected!这些 Mockito 断言断言调用了一个 dao 方法,而不是它做了预期的事情! You will get your coverage numbers but you are more or less binding your tests to an implementation of the dao.您将获得覆盖率数字,但您或多或少地将测试绑定到 dao 的实现。 Ouch.哎哟。

Now this example assumed the service had no real business logic.现在这个例子假设服务没有真正的业务逻辑。 Normally the services will have business logic in addtion to dao calls, and you surely must test those.通常,除了 dao 调用之外,服务还会具有业务逻辑,您肯定必须测试这些。

Now, for unit testing daos, I like to use an embedded database.现在,对于单元测试 daos,我喜欢使用嵌入式数据库。

private EmbeddedDatabase database;
private EventDaoJdbcImpl eventDao = new EventDaoJdbcImpl();

@Before
public void setUp() {
    database = new EmbeddedDatabaseBuilder()
            .setType(EmbeddedDatabaseType.H2)
            .addScript("schema.sql")
            .addScript("init.sql")
            .build();
    eventDao.jdbcTemplate = new JdbcTemplate(database);
}

@Test
public void creatingIncrementsSize() {
    Event e = new Event(9, "Company Softball Game");

    int initialCount = eventDao.findNumberOfEvents();
    eventDao.createEvent(e);
    assertThat(eventDao.findNumberOfEvents(), is(initialCount + 1));
}

@Test
public void deletingDecrementsSize() {
    Event e = new Event(1, "Poker Night");

    int initialCount = eventDao.findNumberOfEvents();
    eventDao.deleteEvent(e);
    assertThat(eventDao.findNumberOfEvents(), is(initialCount - 1));
}

@Test
public void createdEventCanBeFound() {
    eventDao.createEvent(new Event(9, "Company Softball Game"));
    Event e = eventDao.findEventById(9);
    assertThat(e.getId(), is(9));
    assertThat(e.getName(), is("Company Softball Game"));
}

@Test
public void updatesToCreatedEventCanBeRead() {
    eventDao.createEvent(new Event(9, "Company Softball Game"));
    Event e = eventDao.findEventById(9);
    e.setName("Cricket Game");
    eventDao.updateEvent(e);
    e = eventDao.findEventById(9);
    assertThat(e.getId(), is(9));
    assertThat(e.getName(), is("Cricket Game"));
}

@Test(expected=EventExistsException.class)
public void creatingDuplicateEventThrowsException() {
    eventDao.createEvent(new Event(1, "Id1WasAlreadyUsed"));
}

@Test(expected=NoSuchEventException.class)
public void updatingNonExistentEventThrowsException() {
    eventDao.updateEvent(new Event(1000, "Unknown"));
}

@Test(expected=NoSuchEventException.class)
public void deletingNonExistentEventThrowsException() {
    eventDao.deleteEvent(new Event(1000, "Unknown"));
}

@Test(expected=NoSuchEventException.class)
public void findingNonExistentEventThrowsException() {
    eventDao.findEventById(1000);
}

@Test
public void countOfInitialDataSetIsAsExpected() {
    assertThat(eventDao.findNumberOfEvents(), is(8));
}

I still call this a unit test even though most people might call it an integration test.尽管大多数人可能将其称为集成测试,但我仍然称其为单元测试。 The embedded database resides in memory, and it is brought up and taken down when the tests run.嵌入式数据库驻留在内存中,并在测试运行时启动和关闭。 But this relies on the fact that the embedded database looks the same as the production database.但这依赖于嵌入式数据库看起来与生产数据库相同的事实。 Will that be the case?会是这样吗? If not, then all that work was pretty useless.如果没有,那么所有这些工作都毫无用处。 If so, then, as you say, these tests are doing anything different than the integration tests.如果是这样,那么,正如您所说,这些测试所做的与集成测试不同。 But I can run them on demand with mvn test and I have the confidence to refactor.但是我可以使用mvn test按需运行它们,并且我有信心进行重构。

Therefor, I write these unit tests anyway and meet my coverage targets.因此,我无论如何都要编写这些单元测试并满足我的覆盖目标。 When I write integration tests, I assert that an HTTP request returns the expected HTTP response.当我编写集成测试时,我断言 HTTP 请求返回预期的 HTTP 响应。 Yeah it subsumes the unit tests, but hey, when you practice TDD you have those unit tests written before your actual dao implementation anyway.是的,它包含了单元测试,但是,嘿,当你练习 TDD 时,无论如何你都会在实际的 dao 实现之前编写这些单元测试。

If you write unit tests after your dao, then of course they are no fun to write.如果你在你的 dao 之后编写单元测试,那么编写它们当然没有乐趣。 The TDD literature is full of warnings about how writing tests after your code feels like make work and no one wants to do it. TDD 文献中充满了关于如何在您的代码感觉像是开始工作并且没有人愿意这样做之后编写测试的警告。

TL;DR: Your integration tests will subsume your unit tests and in that sense the unit tests are not adding real testing value. TL;DR:您的集成测试将包含您的单元测试,从这个意义上说,单元测试并没有增加真正的测试价值。 However when you have a high-coverage unit test suite you have the confidence to refactor.但是,当您拥有高覆盖率的单元测试套件时,您就有信心进行重构。 But of course if the dao is trivially calling Spring's data access template, then you might not be refactoring.但是当然,如​​果 dao 只是简单地调用 Spring 的数据访问模板,那么您可能不是在重构。 But you never know.但你永远不知道。 And finally, though, if the unit tests are written first in TDD style, you are going to have them anyway.最后,如果单元测试首先以 TDD 风格编写,那么无论如何你都会拥有它们。

You only really need to unit test each layer in isolation if you plan to have the layers exposed to other components out of your project.如果您计划将层暴露给项目之外的其他组件,您只需要单独对每一层进行单元测试。 For a web app, the only way the the repository layer can be invoked, is by the services layer, and the only way the service layer can be invoked is by the controller layer.对于 Web 应用程序,调用存储库层的唯一方式是通过服务层,而调用服务层的唯一方式是通过控制器层。 So testing can start and end at the controller layer.所以测试可以在控制器层开始和结束。 For background tasks, these are invoked in the service layer, so need to be tested here.对于后台任务,这些都是在服务层调用的,这里需要测试一下。

Testing with a real database is pretty fast these days, so doesn't slow your tests down too much, if you design your setup/tear down well.如今,使用真实数据库进行测试的速度非常快,因此如果您将设置/拆卸设计得很好,则不会减慢您的测试速度。 However, if there are any other dependancies that could be slow, or problematic, then these should be mocked/stubbed.但是,如果有任何其他可能缓慢或有问题的依赖项,则应该模拟/存根这些依赖项。

This approach will give you:这种方法会给你:

  • good coverage良好的覆盖
  • realistics tests现实测试
  • minimum amount of effort最少的努力
  • minimum amount of refectoring effort最少的反思努力

However, testing layers in isolation does allow your team to work more concurrently, so one dev can do repository and another can do service for one piece of functionality, and produce independently tested work.但是,隔离的测试层确实允许您的团队更多地并发工作,因此一个开发人员可以做存储库,另一个可以为一项功能提供服务,并产生独立测试的工作。

There will always be double coverage when selenium/functional tests are incorporated as you can't rely on these alone as they are too slow to run.结合硒/功能测试时,总会有双重覆盖,因为您不能单独依赖它们,因为它们运行速度太慢。 However, functional tests dont necessarily need to cover all the code, core functionality alone can be sufficient, aslong as the code has been covered by unit/integration tests.然而,功能测试不一定需要覆盖所有代码,只要代码已经被单元/集成测试覆盖,仅核心功能就足够了。

I think there are two advantages of having finer grained(I will not use intentionaly the word unit test here) tests besides the high end integration tests.我认为除了高端集成测试之外,更细粒度的测试(我不会在这里故意使用单元测试这个词)有两个优点。

1) Redundancy, having the layers covered in more than one place acts like a switch. 1) 冗余,将层覆盖在一个以上的地方就像一个开关。 If one set of tests (the integration test f.ex.) fail to locate the error the second layer may catch it.如果一组测试(集成测试 f.ex.)未能定位错误,第二层可能会捕获它。 I will draw a comparison here with electric switches where redundancy is a must.我将在这里与必须有冗余的电气开关进行比较。 You have a main switch and a specialized switch.您有一个主开关和一个专用开关。

2)Lets suppose that you have a process calling external service. 2)假设您有一个调用外部服务的进程。 For one or another reason (Bug) the original exception gets consumer and an exception that does not carry information about the technical nature of the error reaches the integration test.由于一种或另一种原因(Bug),原始异常得到了消费者,并且不携带有关错误技术性质的信息的异常到达了集成测试。 The integration test will catch the error, but you will have no clue about what the error is or where is it coming from.集成测试将捕获错误,但您将不知道错误是什么或它来自哪里。 Having a finer grained test in place increases the chance of pointing in the correct direction what and where excactly has failed.进行更细粒度的测试会增加指出正确方向的机会,即究竟是什么地方失败了。

I personally think that certain level of redundancy in testing is not a bad thing.我个人认为测试中有一定程度的冗余并不是一件坏事。

In your particular case if you write a CRUD test with in memory database you will have the chance to test your Hibernate mapping layer which can be quite complex if you are using things like Cascading , or fetching and so on...在您的特定情况下,如果您使用内存数据库编写 CRUD 测试,您将有机会测试您的 Hibernate 映射层,如果您使用 Cascading 或 fetching 等内容,这可能会非常复杂......

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 我应该如何在没有 xml 的情况下加载 Spring 的 ApplicationContext 进行单元测试? - How should I load Spring's ApplicationContext without xml for unit-tests? Spring:单元和集成测试 - Spring: unit and integration tests 我应该为此特定方法创建2个单元测试吗? Spring MVC应用 - Should I create 2 unit tests for this specific method? Spring MVC app 是否可以在具有JNDI和spring上下文的Jetty容器中运行TestNG集成测试? - Is it possible to run TestNG integration-tests in a Jetty container with JNDI's and spring context? Spring Boot / JUnit,为多个配置文件运行所有单元测试 - Spring Boot / JUnit, run all unit-tests for multiple profiles 当我尝试在单元测试中模拟侦探时,Spring Cloud失败 - spring cloud is failing when I try to mock sleuth in unit tests 是否应该在单元测试中使用单独的beans.xml配置实例化对象? - Should I use a seperate beans.xml configuration for instantiating objects in my unit tests? 如何为私有服务方法和内部私有方法编写单元测试? - How can I write unit tests for private methods of service and for public methods with private inside? 字段注入与构造函数注入 - 它对我编写单元测试的方式有何影响? - Field injection vs constructor injection - how does it make a difference in the way I write unit tests? Spring + Maven:用于单元测试和集成测试的单独属性文件 - Spring + Maven: separate property files for unit tests and integration tests
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM