怎样编写测试类测试分支
很难为干净的代码找到一个好的定义,因为我们每个人都有自己的单词clean的定义。 但是,有一个似乎是通用的定义:
简洁的代码易于阅读。
这可能会让您感到有些惊讶,但我认为该定义也适用于测试代码。 使测试尽可能具有可读性是我们的最大利益,因为:
- 如果我们的测试易于阅读,那么很容易理解我们的代码是如何工作的。
- 如果我们的测试易于阅读,那么如果测试失败(不使用调试器),很容易发现问题。
编写干净的测试并不难,但是需要大量的实践,这就是为什么如此多的开发人员为此苦苦挣扎的原因。
我也为此感到挣扎,这就是为什么我决定与您分享我的发现的原因。
这是本教程的第一部分,介绍了如何编写干净的测试。 这次,我们将学习如何以简单干净的方式配置测试用例。
问题
假设我们必须使用Spring MVC Test框架为Spring MVC控制器编写“单元测试”。 我们要测试的第一个控制器称为TodoController ,但是我们还必须为应用程序的其他控制器编写“单元测试”。
作为开发人员,我们知道重复的代码是一件坏事。 在编写代码时,我们遵循“ 不要重复自己(DRY)”原则 ,该原则指出:
每条知识都必须在系统中具有单一,明确,权威的表示形式。
我怀疑这是开发人员经常在其测试套件中使用继承的原因之一。 他们将继承视为重用代码和配置的廉价且简便的方法。 这就是为什么他们将所有通用代码和配置放在实际测试类的基类中的原因。
让我们看看如何使用该方法配置“单元测试”。
首先 ,我们必须创建一个抽象基类, 该基类可以配置Spring MVC Test框架,并通过实现setUpTest(MockMvc mockMvc)方法来确保其子类可以提供其他配置。
AbstractControllerTest类的源代码如下所示:
import org.junit.Before;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {WebUnitTestContext.class})
@WebAppConfiguration
public abstract class AbstractControllerTest {private MockMvc mockMvc;@Autowiredprivate WebApplicationContext webAppContext;@Beforepublic void setUp() {mockMvc = MockMvcBuilders.webAppContextSetup(webAppContext).build();setupTest(MockMvc mockMvc)}protected abstract void setUpTest(MockMvc mockMvc);
}
其次 ,我们必须实现实际的测试类,该类创建所需的模拟和新的控制器对象。 TodoControllerTest类的源代码如下所示:
import org.mockito.Mockito;
import org.springframework.test.web.servlet.MockMvc;public class TodoControllerTest extends AbstractControllerTest {private MockMvc mockMvc;@Autowiredprivate TodoService serviceMock;@Overrideprotected void setUpTest(MockMvc mockMvc) {Mockito.reset(serviceMock);this.mockMvc = mockMvc;}//Add test methods here
}
这个测试类看起来很干净,但是有一个主要缺陷:
如果我们想了解测试用例的配置方式,我们必须阅读TodoControllerTest和AbstractControllerTest类的源代码。
这似乎是一个小问题,但这意味着我们必须将注意力从测试用例转移到基类(或多个类)上。 这需要精神上的上下文切换,并且上下文切换非常昂贵 。
您可能当然会争辩说,在这种情况下使用继承的精神代价很低,因为配置非常简单。 的确如此,但是要记住,现实生活中的情况并非总是如此。
上下文切换的实际成本取决于测试类层次结构的深度和配置的复杂性。
解决方案
我们可以通过配置测试类中的所有测试用例来提高配置的可读性。 这意味着我们必须:
- 将所需的注释(例如@RunWith )添加到测试类。
- 将设置和拆卸方法添加到测试类。
如果我们遵循以下规则修改示例测试类,则其源代码如下:
import org.junit.Before;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {WebUnitTestContext.class})
@WebAppConfiguration
public class TodoControllerTest {private MockMvc mockMvc;@Autowiredprivate TodoService serviceMock;@Autowiredprivate WebApplicationContext webAppContext;@Beforepublic void setUp() {Mockito.reset(serviceMock);mockMvc = MockMvcBuilders.webAppContextSetup(webAppContext).build();}//Add test methods here
}
在我看来,我们的测试用例的新配置看起来比旧的配置简单且干净得多,旧的配置分为TodoControllerTest和AbstractControllerTest类。
不幸的是,没有什么是免费的。
这是一个权衡
每个软件设计决策都需要权衡利弊。 这不是该规则的例外 。
在测试类中配置我们的测试用例具有以下好处:
- 我们无需阅读测试类的所有超类就可以了解测试用例的配置。 这样可以节省大量时间,因为我们不必将注意力从一堂课转移到另一堂课。 换句话说, 我们不必付出上下文切换的代价 。
- 当测试失败时,可以节省时间。 如果为了避免重复的代码或配置而使用继承,则很可能我们的基类将包含与某些但不是全部测试用例相关的组件。 换句话说,我们将弄清楚哪些组件与失败的测试用例相关,这可能不是一件容易的事。 在测试类中配置测试用例时, 我们知道每个组件都与失败的测试用例有关 。
另一方面,这种方法的缺点是:
- 我们必须编写重复的代码。 这比将所需的配置放到一个或多个基类上花费的时间更长。
- 如果任何使用的库以迫使我们修改测试配置的方式进行更改,则我们必须对每个测试类进行必要的更改。 这显然比仅对基类(或多个基类)进行这些操作要慢得多。
如果我们唯一的目标是尽可能快地编写测试,那么很明显,我们应该消除重复的代码和配置。
但是,这不是我唯一的目标。
我认为这种方法的优点胜于缺点的原因有两个:
- 继承不是重用代码或配置的正确工具 。
- 如果测试用例失败,我们必须尽快找到并解决问题,并且干净的配置将帮助我们实现该目标。
我在这件事上的立场是明确的。 但是,仍然存在一个非常重要的问题:
您会做出其他折衷吗?
翻译自: https://www.javacodegeeks.com/2014/05/writing-clean-tests-it-starts-from-the-configuration.html
怎样编写测试类测试分支