c++返回指针时候注意提防

很难为干净的代码找到一个好的定义,因为我们每个人都有自己的单词clean的定义。 但是,有一个似乎是通用的定义:

简洁的代码易于阅读。

这可能会让您感到有些惊讶,但我认为该定义也适用于测试代码。 使测试尽可能具有可读性是我们的最大利益,因为:

  • 如果我们的测试易于阅读,那么很容易理解我们的代码是如何工作的。
  • 如果我们的测试易于阅读,那么如果测试失败(不使用调试器),很容易发现问题。

编写干净的测试并不难,但是需要大量的实践,这就是为什么如此多的开发人员为此苦苦挣扎的原因。

我也为此感到挣扎,这就是为什么我决定与您分享我的发现的原因。

这是本教程的第三部分,介绍了如何编写干净的测试。 这次,我们将学习两种可用于从测试中删除幻数的技术。

救援常量

我们使用在我们的代码常量,因为没有常量我们的代码将与被散落幻数 。 使用幻数有两个结果:

  1. 我们的代码很难阅读,因为幻数只是没有意义的值。
  2. 我们的代码很难维护,因为如果必须更改幻数的值,则必须查找该幻数的所有出现并更新每个幻数。

换一种说法,

  • 常数帮助我们用描述其存在原因的某种事物代替幻数。
  • 常量使我们的代码更易于维护,因为如果常量的值发生变化,我们只需将该更改仅保留到一个位置即可。

如果我们考虑从测试案例中找到的幻数,我们会注意到它们可以分为两组:

  1. 与单个测试类相关的幻数。 这种幻数的典型示例是在测试方法中创建的对象的属性值。 我们应该在测试类中声明这些常量
  2. 与多个测试类别相关的幻数。 这种魔术数字的一个很好的例子是由Spring MVC控制器处理的请求的内容类型。 我们应该将这些常量添加到非实例化类中

让我们仔细看看这两种情况。

在测试类中声明常量

那么,为什么我们要在测试类中声明一些常量呢?

毕竟,如果我们考虑使用常量的好处,首先想到的是,我们应该通过创建包含测试中使用的常量的类来消除测试中的幻数。 例如,我们可以创建一个TodoConstants类,其中包含TodoControllerTestTodoCrudServiceTestTodoTest类中使用的常量。

这是一个坏主意

尽管有时候以这种方式共享数据是明智的,但是我们不应该轻易做出这个决定,因为在大多数情况下,我们在测试中引入常数的唯一动机是避免输入错误和幻数。

另外,如果幻数仅与单个测试类相关,则将这种依赖关系引入我们的测试是没有道理的,只是因为我们要最大程度地减少创建的常量的数量。

我认为,处理这种情况的最简单方法是在测试类中声明常量。

让我们找出如何改进本教程前面部分中描述的单元测试。 编写该单元测试以测试RepositoryUserService类的registerNewUserAccount()方法,并且当使用社交符号提供者和唯一的电子邮件地址创建新的用户帐户时,它验证此方法是否正常工作。

该测试用例的源代码如下所示:

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.runners.MockitoJUnitRunner;
import org.mockito.stubbing.Answer;
import org.springframework.security.crypto.password.PasswordEncoder;import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.mockito.Matchers.isA;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;@RunWith(MockitoJUnitRunner.class)
public class RepositoryUserServiceTest {private RepositoryUserService registrationService;@Mockprivate PasswordEncoder passwordEncoder;@Mockprivate UserRepository repository;@Beforepublic void setUp() {registrationService = new RepositoryUserService(passwordEncoder, repository);}@Testpublic void registerNewUserAccount_SocialSignInAndUniqueEmail_ShouldCreateNewUserAccountAndSetSignInProvider() throws DuplicateEmailException       {RegistrationForm registration = new RegistrationForm();registration.setEmail("john.smith@gmail.com");registration.setFirstName("John");registration.setLastName("Smith");registration.setSignInProvider(SocialMediaService.TWITTER);when(repository.findByEmail("john.smith@gmail.com")).thenReturn(null);when(repository.save(isA(User.class))).thenAnswer(new Answer<User>() {@Overridepublic User answer(InvocationOnMock invocation) throws Throwable {Object[] arguments = invocation.getArguments();return (User) arguments[0];}});User createdUserAccount = registrationService.registerNewUserAccount(registration);assertEquals("john.smith@gmail.com", createdUserAccount.getEmail());assertEquals("John", createdUserAccount.getFirstName());assertEquals("Smith", createdUserAccount.getLastName());assertEquals(SocialMediaService.TWITTER, createdUserAccount.getSignInProvider());assertEquals(Role.ROLE_USER, createdUserAccount.getRole());assertNull(createdUserAccount.getPassword());verify(repository, times(1)).findByEmail("john.smith@gmail.com");verify(repository, times(1)).save(createdUserAccount);verifyNoMoreInteractions(repository);verifyZeroInteractions(passwordEncoder);}
}

问题在于,此测试用例在创建新的RegistrationForm对象,配置UserRepository模拟的行为,验证返回的User对象的信息是否正确以及验证是否调用了UserRepository模拟的正确方法时使用了幻数。在经过测试的服务方法中。

通过在测试类中声明常量来删除这些幻数之后,测试的源代码如下所示:

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.runners.MockitoJUnitRunner;
import org.mockito.stubbing.Answer;
import org.springframework.security.crypto.password.PasswordEncoder;import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.mockito.Matchers.isA;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;@RunWith(MockitoJUnitRunner.class)
public class RepositoryUserServiceTest {private static final String REGISTRATION_EMAIL_ADDRESS = "john.smith@gmail.com";private static final String REGISTRATION_FIRST_NAME = "John";private static final String REGISTRATION_LAST_NAME = "Smith";private static final Role ROLE_REGISTERED_USER = Role.ROLE_USER;private static final SocialMediaService SOCIAL_SIGN_IN_PROVIDER = SocialMediaService.TWITTER;private RepositoryUserService registrationService;@Mockprivate PasswordEncoder passwordEncoder;@Mockprivate UserRepository repository;@Beforepublic void setUp() {registrationService = new RepositoryUserService(passwordEncoder, repository);}@Testpublic void registerNewUserAccount_SocialSignInAndUniqueEmail_ShouldCreateNewUserAccountAndSetSignInProvider() throws DuplicateEmailException       {RegistrationForm registration = new RegistrationForm();registration.setEmail(REGISTRATION_EMAIL_ADDRESS);registration.setFirstName(REGISTRATION_FIRST_NAME);registration.setLastName(REGISTRATION_LAST_NAME);registration.setSignInProvider(SOCIAL_SIGN_IN_PROVIDER);when(repository.findByEmail(REGISTRATION_EMAIL_ADDRESS)).thenReturn(null);when(repository.save(isA(User.class))).thenAnswer(new Answer<User>() {@Overridepublic User answer(InvocationOnMock invocation) throws Throwable {Object[] arguments = invocation.getArguments();return (User) arguments[0];}});User createdUserAccount = registrationService.registerNewUserAccount(registration);assertEquals(REGISTRATION_EMAIL_ADDRESS, createdUserAccount.getEmail());assertEquals(REGISTRATION_FIRST_NAME, createdUserAccount.getFirstName());assertEquals(REGISTRATION_LAST_NAME, createdUserAccount.getLastName());assertEquals(SOCIAL_SIGN_IN_PROVIDER, createdUserAccount.getSignInProvider());assertEquals(ROLE_REGISTERED_USER, createdUserAccount.getRole());assertNull(createdUserAccount.getPassword());verify(repository, times(1)).findByEmail(REGISTRATION_EMAIL_ADDRESS);verify(repository, times(1)).save(createdUserAccount);verifyNoMoreInteractions(repository);verifyZeroInteractions(passwordEncoder);}
}

此示例说明在测试类中声明常量具有三个好处:

  1. 我们的测试用例更易于阅读,因为魔术数字被正确命名的常量所替代。
  2. 我们的测试用例更易于维护,因为我们可以更改常量的值而无需对实际测试用例进行任何更改。
  3. RepositoryUserService类的registerNewUserAccount()方法编写新测试更加容易,因为我们可以使用常量而不是幻数。 这意味着我们不必担心拼写错误。

但是,有时我们的测试使用的幻数确实与多个测试类别相关。 让我们找出如何应对这种情况。

将常量添加到非实例性类

如果该常量与多个测试类相关,则在使用该常量的每个测试类中声明该常量是没有意义的。 让我们看一下一种情况,在这种情况下可以向非实例化类添加常量。

假设我们必须为REST API编写两个单元测试:

  • 第一个单元测试确保我们不能向数据库添加空的待办事项。
  • 第二个单元测试确保我们不能向数据库添加空笔记。

这些单元测试使用Spring MVC测试框架。 如果您不熟悉它,则可能要看一看我的
Spring MVC测试教程 。

第一个单元测试的源代码如下所示:

import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
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;import java.nio.charset.Charset;import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {WebUnitTestContext.class})
@WebAppConfiguration
public class TodoControllerTest {private static final MediaType APPLICATION_JSON_UTF8 = new MediaType(MediaType.APPLICATION_JSON.getType(), MediaType.APPLICATION_JSON.getSubtype(),Charset.forName("utf8"));private MockMvc mockMvc;@Autowiredprivate ObjectMapper objectMapper;@Autowiredprivate WebApplicationContext webAppContext;@Beforepublic void setUp() {mockMvc = MockMvcBuilders.webAppContextSetup(webAppContext).build();}@Testpublic void add_EmptyTodoEntry_ShouldReturnHttpRequestStatusBadRequest() throws Exception {TodoDTO addedTodoEntry = new TodoDTO();mockMvc.perform(post("/api/todo").contentType(APPLICATION_JSON_UTF8).content(objectMapper.writeValueAsBytes(addedTodoEntry))).andExpect(status().isBadRequest());}
}

第二个单元测试的源代码如下所示:

import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
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;import java.nio.charset.Charset;import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {WebUnitTestContext.class})
@WebAppConfiguration
public class NoteControllerTest {private static final MediaType APPLICATION_JSON_UTF8 = new MediaType(MediaType.APPLICATION_JSON.getType(), MediaType.APPLICATION_JSON.getSubtype(),Charset.forName("utf8"));private MockMvc mockMvc;@Autowiredprivate ObjectMapper objectMapper;@Autowiredprivate WebApplicationContext webAppContext;@Beforepublic void setUp() {mockMvc = MockMvcBuilders.webAppContextSetup(webAppContext).build();}@Testpublic void add_EmptyNote_ShouldReturnHttpRequestStatusBadRequest() throws Exception {NoteDTO addedNote = new NoteDTO();mockMvc.perform(post("/api/note").contentType(APPLICATION_JSON_UTF8).content(objectMapper.writeValueAsBytes(addedNote))).andExpect(status().isBadRequest());}
}

这两个测试类都声明一个名为APPLICATION_JSON_UTF8的常量。 该常数指定请求的内容类型和字符集。 同样,很明显,在每个测试类中都需要此常量,其中包含用于控制器方法的测试。

这是否意味着我们应该在每个这样的测试类中声明此常量?

没有!

由于以下两个原因,我们应将此常量移至非实例化类:

  1. 它与多个测试类别相关。
  2. 将其移到一个单独的类中可使我们更轻松地为控制器方法编写新测试并维护现有测试。

让我们创建一个最终的WebTestConstants类,将APPLICATION_JSON_UTF8常量移动到该类,然后向创建的类添加一个私有构造函数。

WebTestConstant类的源代码如下所示:

import org.springframework.http.MediaType;public final class WebTestConstants {public static final MediaType APPLICATION_JSON_UTF8 = new MediaType(MediaType.APPLICATION_JSON.getType(), MediaType.APPLICATION_JSON.getSubtype(),Charset.forName("utf8"));private WebTestConstants() {}
}

完成此操作后,我们可以从测试类中删除APPLICATION_JSON_UTF8常量。 我们的新测试的源代码如下所示:

import com.fasterxml.jackson.databind.ObjectMapper;
import net.petrikainulainen.spring.jooq.config.WebUnitTestContext;
import net.petrikainulainen.spring.jooq.todo.dto.TodoDTO;
import org.junit.Before;
import org.junit.Test;
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;import java.nio.charset.Charset;import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {WebUnitTestContext.class})
@WebAppConfiguration
public class TodoControllerTest {private MockMvc mockMvc;@Autowiredprivate ObjectMapper objectMapper;@Autowiredprivate WebApplicationContext webAppContext;@Beforepublic void setUp() {mockMvc = MockMvcBuilders.webAppContextSetup(webAppContext).build();}@Testpublic void add_EmptyTodoEntry_ShouldReturnHttpRequestStatusBadRequest() throws Exception {TodoDTO addedTodoEntry = new TodoDTO();mockMvc.perform(post("/api/todo").contentType(WebTestConstants.APPLICATION_JSON_UTF8).content(objectMapper.writeValueAsBytes(addedTodoEntry))).andExpect(status().isBadRequest());}
}

我们刚刚从测试类中删除了重复的代码,并减少了为控制器编写新测试所需的工作。 太酷了吧?

如果我们更改添加到常量类的常量的值,则此更改将影响使用该常量的每个测试用例。 这就是为什么我们应该最小化添加到常量类的常量的数量

摘要

现在我们知道,常数可以帮助我们编写干净的测试,并减少编写新测试和维护现有测试所需的工作量。 将本博客文章中给出的建议付诸实践时,我们需要记住以下几点:

  • 我们必须给常量和常量类起好名字 。 如果我们不这样做,就不会利用这些技术的全部潜力。
  • 我们不应该在不弄清楚我们想要用该常数实现什么的情况下引入新常数。 实际情况通常比此博客文章的示例复杂得多。 如果我们在自动驾驶仪上编写代码,很可能会错过针对当前问题的最佳解决方案。

翻译自: https://www.javacodegeeks.com/2014/05/writing-clean-tests-beware-of-magic.html

c++返回指针时候注意提防

c++返回指针时候注意提防_编写干净的测试–提防魔术相关推荐

  1. 怎样编写测试类测试分支_编写干净的测试-被认为有害的新内容

    怎样编写测试类测试分支 很难为干净的代码找到一个好的定义,因为我们每个人都有自己的单词clean的定义. 但是,有一个似乎是通用的定义: 简洁的代码易于阅读. 这可能会让您感到有些惊讶,但我认为该定义 ...

  2. 断言工具的编写_编写干净的测试–用特定领域的语言替换断言

    断言工具的编写 很难为干净的代码找到一个好的定义,因为我们每个人都有自己的单词clean的定义. 但是,有一个似乎是通用的定义: 简洁的代码易于阅读. 这可能会让您感到有些惊讶,但我认为该定义也适用于 ...

  3. 分而治之_编写干净的测试–分而治之

    分而治之 好的单元测试应该仅出于一个原因而失败. 这意味着适当的单元测试仅测试一个逻辑概念. 如果我们要编写干净的测试,则必须识别那些逻辑概念,并且每个逻辑概念只编写一个测试用例. 这篇博客文章描述了 ...

  4. 编写干净的测试–提防魔术

    很难为干净的代码找到一个好的定义,因为我们每个人都有自己的单词clean的定义. 但是,有一个似乎是通用的定义: 干净的代码易于阅读. 这可能会让您感到有些惊讶,但是我认为该定义也适用于测试代码. 使 ...

  5. 怎样编写测试类测试分支_编写干净的测试–天堂中的麻烦

    怎样编写测试类测试分支 如果我们的代码有明显的错误,我们很有动力对其进行改进. 但是,在某些时候,我们认为我们的代码"足够好"并继续前进. 通常,当我们认为改进现有代码的好处小于所 ...

  6. 怎样编写测试类测试分支_编写干净的测试–从配置开始

    怎样编写测试类测试分支 很难为干净的代码找到一个好的定义,因为我们每个人都有自己的单词clean的定义. 但是,有一个似乎是通用的定义: 简洁的代码易于阅读. 这可能会让您感到有些惊讶,但我认为该定义 ...

  7. 编写干净的测试-被认为有害的新内容

    很难为干净的代码找到一个好的定义,因为我们每个人都有自己的单词clean的定义. 但是,有一个似乎是通用的定义: 干净的代码易于阅读. 这可能会让您感到有些惊讶,但是我认为该定义也适用于测试代码. 使 ...

  8. 编写干净的测试–用特定领域的语言替换断言

    很难为干净的代码找到一个好的定义,因为我们每个人都有自己的单词clean的定义. 但是,有一个似乎是通用的定义: 干净的代码易于阅读. 这可能会让您感到有些惊讶,但是我认为该定义也适用于测试代码. 使 ...

  9. 编写干净的测试–分而治之

    好的单元测试应该仅出于一个原因而失败. 这意味着适当的单元测试仅测试一个逻辑概念. 如果我们要编写干净的测试,则必须识别这些逻辑概念,并且每个逻辑概念仅编写一个测试用例. 这篇博客文章描述了我们如何识 ...

最新文章

  1. cygwin中 unable get setup.ini from... 的错误问题
  2. php下关于Cannot use a scalar value as an array的解决办法
  3. (转)Linux下MatlabCompilerRuntime的安装和使用
  4. LeetCode Algorithm 451. 根据字符出现频率排序
  5. 求int在二进制存储时1的个数(C++)
  6. Bootstrap组件1_字体图标
  7. SAP OData schema version and backend version
  8. workflow initialization - GSWFWC
  9. 攀枝花学院计算机专业单身率,重磅 | 攀枝花学院2018届毕业生就业质量年度报告...
  10. 15款13英寸低配mbp的外接显示器选择
  11. 《王道》数据结构笔记整理2022
  12. 领域驱动设计核心概念
  13. Appinventor——蓝牙app(蓝牙遥控器、串口助手、温湿度显示、切换界面蓝牙依旧保持连接)
  14. kindle paper write1 kindle pw1 安装多看系统 or 原本是多看双系统,升级kindle系统后多看系统消失,解决方法降级kindle系统+重装kindle系统
  15. mac查看内存使用情况命令
  16. Android studio 分渠道打包,引用不同的moudle
  17. Qt: QStringList去除重复元素
  18. 苹果xsmax怎么开机_苹果xsmax触屏不灵敏,xsmax触屏失灵怎么回事
  19. Java如何获取IP属地 ip2region failed to create searcher with x:java.io.FileNotFoundException:( 系统找不到指定的路径)
  20. Redis集群搭建(转自一菲聪天的“Windows下搭建Redis集群”)

热门文章

  1. 背包例题【dp练习】
  2. 2018 ACM ICPC Asia Regional - Seoul B.Cosmetic Survey
  3. UVALive 8518 - Sum of xor sum
  4. 阿里微服务架构下分布式事务Seata
  5. Spring 注入内部 Beans
  6. vue 动态修改路由参数
  7. JavaFX将Node导出为图片
  8. 秒杀系统设计的 5 个要点:前端三板斧+后端两条路
  9. Oracle入门(十四.21)之创建DML触发器:第二部分
  10. 【FTP】发布FTP服务器