呢图网品牌网络seo方案外包
呢图网,品牌网络seo方案外包,自己制作广告图片软件,合肥做网站首选 晨飞网络怎样编写测试类测试分支很难为干净的代码找到一个好的定义#xff0c;因为我们每个人都有自己的单词clean的定义。 但是#xff0c;有一个似乎是通用的定义#xff1a; 简洁的代码易于阅读。 这可能会让您感到有些惊讶#xff0c;但我认为该定义也适用于测试代码。 使测试… 怎样编写测试类测试分支 很难为干净的代码找到一个好的定义因为我们每个人都有自己的单词clean的定义。 但是有一个似乎是通用的定义 简洁的代码易于阅读。 这可能会让您感到有些惊讶但我认为该定义也适用于测试代码。 使测试尽可能具有可读性是我们的最大利益因为 如果我们的测试易于阅读那么很容易理解我们的代码是如何工作的。 如果我们的测试易于阅读那么如果测试失败不使用调试器很容易发现问题。 编写干净的测试并不难但是需要大量的实践这就是为什么如此多的开发人员为此苦苦挣扎的原因。 我也为此感到挣扎这就是为什么我决定与您分享我的发现的原因。 这是我教程的第四部分描述了我们如何编写干净的测试。 这次我们将学习为什么不使用new关键字在测试方法中创建对象。 我们还将学习如何用工厂方法和测试数据构建器替换new关键字。 新不是新黑 在本教程中我们一直在重构单元测试以确保当使用唯一的电子邮件地址和社交登录提供者创建新用户帐户时 RepositoryUserService类的registerNewUserAccountRegistrationForm userAccountData方法能够按预期工作。 RegistrationForm类是一个数据传输对象DTO 我们的单元测试使用setter方法设置其属性值。 我们的单元测试的源代码如下所示相关代码突出显示 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.smithgmail.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 AnswerUser() {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);}
} 那么有什么问题呢 我们的单元测试中突出显示的部分很短而且相对容易阅读。 我认为此代码的最大问题是它是以数据为中心的。 它创建了一个新的RegistrationForm对象并设置了创建对象的属性值但没有描述这些属性值的含义。 如果我们使用new关键字在测试方法中创建新对象则由于以下原因我们的测试将变得难以阅读 读者必须知道所创建对象的不同状态。 例如如果我们考虑示例读者必须知道如果我们创建一个新的RegistrationForm对象并设置email firstName lastName和signInProvider属性的属性值则意味着该对象是一个注册即通过使用社交登录提供商进行。 如果创建的对象具有许多属性则创建该对象的代码会乱码我们测试的源代码。 我们应该记住即使我们在测试中需要这些对象我们也应该专注于描述被测试方法/功能的行为。 尽管不能完全消除这些缺点是不现实的但我们应尽最大努力将其影响降到最低并使我们的测试尽可能易于阅读。 让我们找出如何使用工厂方法来做到这一点。 使用工厂方法 当我们使用工厂方法创建新对象时我们应该以这种方式命名工厂方法及其方法参数以使我们的代码更易于读写。 让我们看一下两种不同的工厂方法看看它们对我们的单元测试的可读性有什么样的影响。 这些工厂方法通常添加到对象母类中因为它们通常对多个测试类有用。 但是由于我想保持简单因此将它们直接添加到测试类中。 第一个工厂方法的名称是newRegistrationViaSocialSignIn 并且没有方法参数。 在将此工厂方法添加到测试类之后单元测试的源如下所示相关部分已突出显示 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.smithgmail.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 newRegistrationViaSocialSignIn();when(repository.findByEmail(REGISTRATION_EMAIL_ADDRESS)).thenReturn(null);when(repository.save(isA(User.class))).thenAnswer(new AnswerUser() {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);}private RegistrationForm newRegistrationViaSocialSignIn() {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);return registration;}
} 第一种工厂方法具有以下后果 我们测试方法的一部分它创建了新的RegistrationForm对象比以前干净得多并且工厂方法的名称描述了所创建的RegistrationForm对象的状态。 我们的模拟对象的配置更难以阅读因为email属性的值在我们的工厂方法中被“隐藏”了。 由于创建的RegistrationForm对象的属性值被“隐藏”在我们的工厂方法中因此我们的断言更难以阅读。 如果使用对象母模式 则问题将更大因为我们必须将相关的常量移至对象母类。 我认为可以说尽管第一种工厂方法有其好处但它也有严重的缺点。 让我们看看第二种工厂方法是否可以消除这些缺点。 第二个工厂方法的名称为newRegistrationViaSocialSignIn 并且它将电子邮件地址名字姓氏和提供程序中的社交符号作为方法参数。 在将此工厂方法添加到测试类之后单元测试的源如下所示相关部分已突出显示 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.smithgmail.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 newRegistrationViaSocialSignIn(REGISTRATION_EMAIL_ADDRESS,REGISTRATION_FIRST_NAME,REGISTRATION_LAST_NAME,SOCIAL_MEDIA_SERVICE);when(repository.findByEmail(REGISTRATION_EMAIL_ADDRESS)).thenReturn(null);when(repository.save(isA(User.class))).thenAnswer(new AnswerUser() {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);}private RegistrationForm newRegistrationViaSocialSignIn(String emailAddress, String firstName, String lastName, SocialMediaService signInProvider) {RegistrationForm registration new RegistrationForm();registration.setEmail(emailAddress);registration.setFirstName(firstName);registration.setLastName(lastName);registration.setSignInProvider(signInProvider);return registration;}
} 第二种工厂方法具有以下后果 我们的测试方法的一部分创建新的RegistrationForm对象比使用第一个工厂方法的相同代码稍微有些混乱。 但是它仍然比原始代码干净因为factory方法的名称描述了创建对象的状态。 似乎消除了第一个工厂方法的弊端因为创建的对象的属性值未“隐藏”在工厂方法内部。 看起来很酷对吧 真的很容易想到天堂里一切都很好但是事实并非如此。 尽管我们已经看到工厂方法可以使我们的测试更具可读性但事实是只有在满足以下条件时它们才是一个不错的选择 工厂方法没有太多的方法参数。 当方法参数的数量增加时我们的测试将变得更加难以读写。 显而易见的问题是工厂方法可以有多少个方法参数 不幸的是很难给出确切的答案但是我认为如果工厂方法只有少数方法参数那么使用工厂方法是一个不错的选择。 测试数据没有太大的差异。 使用工厂方法的问题是单个工厂方法通常适用于一个用例。 如果我们需要支持N个用例则需要N种工厂方法。 这是一个问题因为随着时间的流逝我们的工厂方法变得ated肿混乱并且难以维护尤其是如果使用对象母模式。 让我们找出测试数据生成器是否可以解决其中一些问题。 使用测试数据构建器 测试数据构建器是使用构建器模式创建新对象的类。 Effective Java中描述的构建器模式有很多好处 但是我们的主要动机是提供一种流畅的API以创建测试中使用的对象。 我们可以按照以下步骤创建一个测试数据构建器类该类创建新的RegistrationForm对象 创建一个RegistrationFormBuilder类。 将RegistrationForm字段添加到创建的类。 该字段包含对创建对象的引用。 将默认构造函数添加到创建的类中并通过创建新的RegistrationForm对象来实现它。 添加用于设置创建的RegistrationForm对象的属性值的方法。 每个方法都通过调用正确的setter方法来设置属性值并返回对RegistrationFormBuilder对象的引用。 请记住这些方法的方法名称可以建立或破坏我们的DSL 。 向所创建的类中添加一个build方法并通过返回所创建的RegistrationForm对象来实现它。 我们的测试数据构建器类的源代码如下所示 public class RegistrationFormBuilder {private RegistrationForm registration;public RegistrationFormBuilder() {registration new RegistrationForm();}public RegistrationFormBuilder email(String email) {registration.setEmail(email);return this;}public RegistrationFormBuilder firstName(String firstName) {registration.setFirstName(firstName);return this;}public RegistrationFormBuilder lastName(String lastName) {registration.setLastName(lastName);return this;}public RegistrationFormBuilder isSocialSignInViaSignInProvider(SocialMediaService signInProvider) {registration.setSignInProvider(signInProvider);return this;}public RegistrationForm build() {return registration;}
} 在修改了单元测试以使用新的测试数据构建器类之后其源代码如下所示相关部分已突出显示 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.smithgmail.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 RegistrationFormBuilder().email(REGISTRATION_EMAIL_ADDRESS).firstName(REGISTRATION_FIRST_NAME).lastName(REGISTRATION_LAST_NAME).isSocialSignInViaSignInProvider(SOCIAL_SIGN_IN_PROVIDER).build();when(repository.findByEmail(REGISTRATION_EMAIL_ADDRESS)).thenReturn(null);when(repository.save(isA(User.class))).thenAnswer(new AnswerUser() {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);}
} 如我们所见测试数据构建器具有以下优点 创建新的RegistrationForm对象的代码易于阅读和编写。 我非常喜欢流畅的API并且我认为这段代码既优美又优雅。 构建器模式可确保从我们的测试数据中发现的变化不再是问题因为我们可以简单地将新方法添加到测试数据构建器类中。 模拟对象和断言的配置易于阅读因为常量在我们的测试方法中可见并且DSL强调每个属性值的含义。 那么我们应该对所有内容使用构建器模式吗 没有 仅在有意义时才应使用测试数据构建器。 换句话说我们应该在以下情况下使用它们 我们设置了许多属性值。 我们的测试数据有很大的差异。 如果满足以下条件之一则构建器模式是一个完美的选择。 原因是我们可以通过命名builder类的setter-like方法来创建特定于域的语言 。 即使我们将创建许多不同的对象并设置许多属性值这也使我们的测试易于读写。 那是建造者木匠的力量。 如果您想了解有关流利API的更多信息则应阅读以下文章 流利的界面 Java Fluent API设计器速成课程 用Java构建流畅的API内部DSL 今天就这些。 让我们继续并总结从这篇博客文章中学到的知识。 摘要 我们了解了为什么使用new关键字在测试方法中创建对象不是一个好主意并且我们学习了两种不同的方法来创建在测试中使用的对象。 更具体地说这篇博客文章教会了我们三件事 通过使用new关键字在测试方法中创建所需的对象是一个坏主意因为它会使我们的测试混乱且难以阅读。 如果我们只需要设置少数几个属性值而我们的测试数据没有太多变化则应该使用工厂方法来创建所需的对象。 如果必须设置很多属性值和/或我们的测试数据有很多差异则应该使用测试数据生成器来创建所需的对象。 翻译自: https://www.javacodegeeks.com/2014/05/writing-clean-tests-new-considered-harmful.html怎样编写测试类测试分支
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/87835.shtml
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!