做车身拉花的网站,plm项目管理系统,苏州设计网页网站好,自己怎么设计公主房如果我们的代码有明显的错误#xff0c;我们很有动力进行改进。 但是#xff0c;在某些时候#xff0c;我们认为我们的代码“足够好”并继续前进。 通常#xff0c;当我们认为改进现有代码的好处小于所需的工作时#xff0c;就会发生这种情况。 当然#xff0c;如果我们… 如果我们的代码有明显的错误我们很有动力进行改进。 但是在某些时候我们认为我们的代码“足够好”并继续前进。 通常当我们认为改进现有代码的好处小于所需的工作时就会发生这种情况。 当然如果我们低估了投资的回报我们可能会打错电话这会伤害我们。 这就是发生在我身上的事情我决定写这篇文章以便您避免犯同样的错误。 编写“良好”单元测试 如果我们要编写“好的”单元测试则必须编写以下单元测试 只测试一件事 。 好的单元测试只能因一个原因而失败并且只能断言一件事。 被正确命名 。 测试方法的名称必须显示测试失败的原因。 模拟外部依赖关系和状态 。 如果单元测试失败我们将确切知道问题出在哪里。 补充阅读 单元测试只能测试一件事情 编写干净的测试命名问题 编写干净的测试分而治之 编写干净的测试验证或不验证 如果我们编写满足这些条件的单元测试我们将编写好的单元测试。 对 我曾经这样认为。 现在我对此表示怀疑 。 善意铺平地狱之路 我从未见过决定编写糟糕的单元测试的软件开发人员。 如果开发人员正在编写单元测试则他/她很有可能要编写好的单元测试。 但是这并不意味着该开发人员编写的单元测试是好的。 我想编写既易于阅读又易于维护的单元测试。 我什至写了一个教程描述了如何编写干净的测试 。 问题在于本教程中给出的建议还不够好尚未。 它可以帮助我们入门但是并没有显示出兔子洞的真正深度。 我的教程中描述的方法存在两个主要问题 命名标准是FTW吗 如果我们使用Roy Osherove引入的“命名标准”则会注意到很难描述被测状态和预期行为。 当我们为简单场景编写测试时此命名标准非常有效。 问题在于真正的软件并不简单。 通常我们最终使用以下两个选项之一来命名测试方法 首先 如果我们尝试尽可能具体则测试方法的方法名称会变得太过糟糕。 最后我们必须承认我们不能像我们想要的那样具体因为方法名称会占用太多空间。 其次 如果我们尝试使方法名称尽可能短则方法名称将不会真正描述测试状态和预期行为。 选择哪个选项实际上并不重要因为无论如何我们都会遇到以下问题 如果测试失败则方法名称不一定描述要出错的方法。 我们可以使用自定义断言来解决此问题但是它们不是免费的。 很难对我们的测试涵盖的场景进行简要概述。 以下是我们在“ 编写干净测试”教程中编写的测试方法的名称 registerNewUserAccount_SocialSignInAndDuplicateEmail_ShouldThrowException registerNewUserAccount_SocialSignInAndDuplicateEmail_ShouldNotSaveNewUserAccount registerNewUserAccount_SocialSignInAndUniqueEmail_ShouldSaveNewUserAccountAndSetSignInProvider registerNewUserAccount_SocialSignInAndUniqueEmail_ShouldReturnCreatedUserAccount registerNewUserAccount_SocialSignInAnquequeEmail_ShouldNotCreateEncodedPasswordForUser 这些方法的名称不是很长但是我们必须记住编写这些单元测试是为了测试一种简单的注册方法。 当我使用这种命名约定为现实生活中的软件项目编写自动化测试时最长的方法名称是我们最长的示例名称的两倍。 那不是很干净或可读。 我们可以做得更好 。 没有通用配置 在本教程中我们使单元测试变得更好了 。 尽管如此他们仍然遭受这样的事实即没有“自然的”方式在不同的单元测试之间共享配置。 这意味着我们的单元测试包含许多重复的代码这些代码配置了我们的模拟对象并创建了在单元测试中使用的其他对象。 另外由于没有“自然”的方式表明某些常量仅与特定的测试方法相关因此我们必须将所有常量添加到测试类的开头。 我们的测试类的源代码如下突出显示有问题的代码 import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
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 com.googlecode.catchexception.CatchException.catchException;
import static com.googlecode.catchexception.CatchException.caughtException;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Matchers.isA;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
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 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_SocialSignInAndDuplicateEmail_ShouldThrowException() 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(new User());catchException(registrationService).registerNewUserAccount(registration);assertThat(caughtException()).isExactlyInstanceOf(DuplicateEmailException.class);}Testpublic void registerNewUserAccount_SocialSignInAndDuplicateEmail_ShouldNotSaveNewUserAccount() 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(new User());catchException(registrationService).registerNewUserAccount(registration);verify(repository, never()).save(isA(User.class));}Testpublic void registerNewUserAccount_SocialSignInAndUniqueEmail_
ShouldSaveNewUserAccountAndSetSignInProvider() 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);registrationService.registerNewUserAccount(registration);ArgumentCaptorUser userAccountArgument ArgumentCaptor.forClass(User.class);verify(repository, times(1)).save(userAccountArgument.capture());User createdUserAccount userAccountArgument.getValue();assertThatUser(createdUserAccount).hasEmail(REGISTRATION_EMAIL_ADDRESS).hasFirstName(REGISTRATION_FIRST_NAME).hasLastName(REGISTRATION_LAST_NAME).isRegisteredUser().isRegisteredByUsingSignInProvider(SOCIAL_SIGN_IN_PROVIDER);}Testpublic void registerNewUserAccount_SocialSignInAndUniqueEmail_ShouldReturnCreatedUserAccount() 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);assertThatUser(createdUserAccount).hasEmail(REGISTRATION_EMAIL_ADDRESS).hasFirstName(REGISTRATION_FIRST_NAME).hasLastName(REGISTRATION_LAST_NAME).isRegisteredUser().isRegisteredByUsingSignInProvider(SOCIAL_SIGN_IN_PROVIDER);}Testpublic void registerNewUserAccount_SocialSignInAnUniqueEmail_ShouldNotCreateEncodedPasswordForUser() 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);registrationService.registerNewUserAccount(registration);verifyZeroInteractions(passwordEncoder);}
} 一些开发人员认为看起来像上面示例的单元测试足够干净。 我理解这种情绪因为我曾经是其中之一。 但是这些单元测试存在三个问题 该案的实质并没有那么清楚 。 因为每种测试方法在调用被测试方法并验证预期结果之前都会进行自我配置所以我们的测试方法变得比必要的更长。 这意味着我们不能只看一眼随机测试方法并弄清楚它要测试什么。 编写新的单元测试很慢 。 因为每个单元测试都必须自行配置所以向我们的测试套件中添加新的单元测试比它可能要慢得多。 另一个“意外”的缺点是这种单元测试鼓励人们练习复制和粘贴编程 。 维持这些单元测试是一件痛苦的事情 。 如果我们向注册表单添加新的必填字段或者更改registerNewUserAccount方法的实现则必须对每个单元测试进行更改。 这些单元测试太脆弱了。 换句话说这些单元测试很难阅读很难编写和维护。 我们必须做得更好 。 摘要 这篇博客文章教会了我们四件事 即使我们认为我们正在编写好的单元测试也不一定是正确的。 如果由于必须更改许多单元测试而导致更改现有功能的速度很慢那么我们就不会编写好的单元测试。 如果添加新功能的速度很慢因为我们必须向单元测试中添加大量重复的代码那么我们就不会编写好的单元测试。 如果我们看不到单元测试所涵盖的情况那么我们就没有编写好的单元测试。 本教程的下一部分将回答这个非常相关的问题 如果现有的单元测试很烂我们该如何解决 如果要编写干净的测试则应阅读我的“ 编写干净的测试”教程 。 翻译自: https://www.javacodegeeks.com/2015/03/writing-clean-tests-trouble-in-paradise.html