编写干净的测试–验证或不验证

在编写使用模拟对象的单元测试时,请遵循以下步骤:

  1. 配置我们的模拟对象的行为。
  2. 调用测试的方法。
  3. 验证是否已调用模拟对象的正确方法。

第三步的描述实际上有点误导,因为通常我们最终会验证是否调用了正确的方法以及未调用模拟对象的其他方法。

每个人都知道,如果我们要编写无错误的软件,我们必须验证这两种情况或不良情况的发生。

对?

让我们验证一切

让我们首先来看一下用于向数据库添加新用户帐户的服务方法的实现。

此服务方法的要求是:

  • 如果注册用户帐户的电子邮件地址不是唯一的,我们的服务方法必须抛出异常。
  • 如果注册的用户帐户具有唯一的电子邮件地址,则我们的服务方法必须将新的用户帐户添加到数据库中。
  • 如果注册的用户帐户具有唯一的电子邮件地址,并且是使用常规登录创建的,则我们的服务方法必须先对用户密码进行编码,然后再将其保存到数据库中。
  • 如果注册的用户帐户具有唯一的电子邮件地址,并且是使用社交登录创建的,则我们的服务方法必须保存使用的社交登录提供商。
  • 通过使用社交登录创建的用户帐户必须没有密码。
  • 我们的服务方法必须返回创建的用户帐户的信息。

如果要了解如何指定服务方法的要求,则应阅读以下博客文章:

  • 从上到下:Web应用程序的TDD
  • 从构思到代码:敏捷规范的生命周期

通过执行以下步骤来实现此服务方法:

  1. 服务方法检查是否从数据库中找不到用户提供的电子邮件地址。 它通过调用UserRepository接口的findByEmail()方法来实现。
  2. 如果找到User对象,则服务方法方法将引发DuplicateEmailException
  3. 它创建一个新的User对象。 如果通过使用常规登录进行注册 (未设置RegistrationForm类的signInProvider属性),则service方法将对用户提供的密码进行编码,并将编码后的密码设置为创建的User对象。
  4. 服务方法将创建的User对象的信息保存到数据库中,并返回保存的User对象。

RepositoryUserService类的源代码如下所示:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;@Service
public class RepositoryUserService implements UserService {private PasswordEncoder passwordEncoder;private UserRepository repository;@Autowiredpublic RepositoryUserService(PasswordEncoder passwordEncoder, UserRepository repository) {this.passwordEncoder = passwordEncoder;this.repository = repository;}@Transactional@Overridepublic User registerNewUserAccount(RegistrationForm userAccountData) throws DuplicateEmailException {if (emailExist(userAccountData.getEmail())) {throw new DuplicateEmailException("The email address: " + userAccountData.getEmail() + " is already in use.");}String encodedPassword = encodePassword(userAccountData);User registered = User.getBuilder().email(userAccountData.getEmail()).firstName(userAccountData.getFirstName()).lastName(userAccountData.getLastName()).password(encodedPassword).signInProvider(userAccountData.getSignInProvider()).build();return repository.save(registered);}private boolean emailExist(String email) {User user = repository.findByEmail(email);if (user != null) {return true;}return false;}private String encodePassword(RegistrationForm dto) {String encodedPassword = null;if (dto.isNormalRegistration()) {encodedPassword = passwordEncoder.encode(dto.getPassword());}return encodedPassword;}
}

如果我们要编写单元测试以确保当用户通过使用社交登录注册新用户帐户时我们的服务方法能够正常工作,并且我们要验证我们的服务方法与模拟对象之间的每一次交互,我们必须编写八个对其进行单元测试。

我们必须确保:

  • 当提供重复的电子邮件地址时,服务方法将检查电子邮件地址是否唯一。
  • 给定重复的电子邮件地址时,将引发DuplicateEmailException
  • 给定重复的电子邮件地址时,service方法不会将新帐户保存到数据库中。
  • 如果提供重复的电子邮件地址,我们的服务方法不会对用户的密码进行编码。
  • 当提供唯一的电子邮件地址时,我们的服务方法会检查电子邮件地址是否唯一。
  • 当给出唯一的电子邮件地址时,我们的服务方法将创建一个包含正确信息的新User对象,并将创建的User对象的信息保存到数据库中。
  • 当给出唯一的电子邮件地址时,我们的服务方法将返回创建的用户帐户的信息。
  • 当指定唯一的电子邮件地址并使用社交登录名时,我们的服务方法不得设置创建的用户帐户的密码(或对其进行编码)。

我们的测试类的源代码如下所示:

import net.petrikainulainen.spring.social.signinmvc.user.dto.RegistrationForm;
import net.petrikainulainen.spring.social.signinmvc.user.dto.RegistrationFormBuilder;
import net.petrikainulainen.spring.social.signinmvc.user.model.SocialMediaService;
import net.petrikainulainen.spring.social.signinmvc.user.model.User;
import net.petrikainulainen.spring.social.signinmvc.user.repository.UserRepository;
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 net.petrikainulainen.spring.social.signinmvc.user.model.UserAssert.assertThatUser;
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.smith@gmail.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_ShouldCheckThatEmailIsUnique() 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, times(1)).findByEmail(REGISTRATION_EMAIL_ADDRESS);}@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_SocialSignInAndDuplicateEmail_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(new User());catchException(registrationService).registerNewUserAccount(registration);verifyZeroInteractions(passwordEncoder);}@Testpublic void registerNewUserAccount_SocialSignInAndUniqueEmail_ShouldCheckThatEmailIsUnique() 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);verify(repository, times(1)).findByEmail(REGISTRATION_EMAIL_ADDRESS);}@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);ArgumentCaptor<User> 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 Answer<User>() {@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);}
}

这些单元测试是按照本教程前面部分中给出的说明编写的。

该课程有很多单元测试。 我们确定他们每个人都是真的必要吗?

或者可能不是

一个明显的问题是,我们编写了两个单元测试,两个单元测试都验证我们的服务方法检查了用户提供的电子邮件地址是否唯一。 我们可以通过将这些测试合并为一个单元测试来解决此问题。 毕竟,一项测试应该使我们相信,我们的服务方法会在创建新用户帐户之前验证用户提供的电子邮件地址是否唯一。

但是,如果这样做,我们将找不到更有趣的问题的答案。 这个问题是:

我们是否应该真的验证测试代码和模拟对象之间的每一次交互?

几个月前,我碰到了James Coplien撰写的标题为: 为什么大多数单元测试都是浪费的文章。 本文提出了几点要点,但其中之一非常适合这种情况。 詹姆斯·科普林(James Coplien)认为,对于测试套件中的每个测试,我们应该提出一个问题:

如果该测试失败,那么将损害哪些业务要求?

他还解释了为什么这是一个如此重要的问题:

在大多数情况下,答案是“我不知道”。 如果您不知道测试的价值,那么从理论上讲,测试的商业价值可能为零。 测试确实要付出代价:维护,计算时间,管理等。 这意味着测试可能具有净负值。 这是要删除的第四类测试。

让我们找出使用此问题评估单元测试时会发生什么。

弹出问题

当问一个问题时:“如果该测试失败,将危及到哪些业务需求?” 关于测试类的每个单元测试,我们得到以下答案:

  • 当提供重复的电子邮件地址时,服务方法将检查电子邮件地址是否唯一。
    • 用户必须具有唯一的电子邮件地址。
  • 给定重复的电子邮件地址时,将引发DuplicateEmailException
    • 用户必须具有唯一的电子邮件地址。
  • 给定重复的电子邮件地址时,service方法不会将新帐户保存到数据库中。
    • 用户必须具有唯一的电子邮件地址。
  • 如果提供重复的电子邮件地址,我们的服务方法不会对用户的密码进行编码。
  • 当提供唯一的电子邮件地址时,我们的服务方法会检查电子邮件地址是否唯一。
    • 用户必须具有唯一的电子邮件地址。
  • 给定唯一的电子邮件地址后,我们的服务方法将创建一个包含正确信息的新User对象,并将创建的User对象的信息保存到使用的数据库中。
    • 如果注册的用户帐户具有唯一的电子邮件地址,则必须将其保存到数据库中。
  • 当给出唯一的电子邮件地址时,我们的服务方法将返回创建的用户帐户的信息。
    • 我们的服务方法必须返回创建的用户帐户的信息。
  • 当指定唯一的电子邮件地址并使用社交登录名时,我们的服务方法不得设置创建的用户帐户的密码(或对其进行编码)。
    • 使用社交登录创建的用户帐户没有密码。

乍一看,我们的测试类似乎只有一个没有业务价值(或可能有负净值)的单元测试。 此单元测试可确保当用户尝试使用重复的电子邮件地址创建新的用户帐户时,我们的代码与PasswordEncoder模拟之间没有任何交互。

很明显,我们必须删除此单元测试,但这不是唯一必须删除的单元测试。

兔子洞比预期的深

早些时候我们注意到我们的测试类包含两个单元测试,两个单元测试都验证是否调用了UserRepository接口的findByEmail()方法。 当我们仔细查看测试的服务方法的实现时,我们注意到:

  • UserRepository接口的findByEmail()方法返回User对象时,我们的服务方法将引发DuplicateEmailException
  • UserRepository接口的findByEmail()方法返回null时,我们的服务方法将创建一个新的用户帐户。

经过测试的服务方法的相关部分如下所示:

public User registerNewUserAccount(RegistrationForm userAccountData) throws DuplicateEmailException {if (emailExist(userAccountData.getEmail())) {//If the PersonRepository returns a Person object, an exception is thrown.throw new DuplicateEmailException("The email address: " + userAccountData.getEmail() + " is already in use.");}//If the PersonRepository returns null, the execution of this method continues.
}private boolean emailExist(String email) {User user = repository.findByEmail(email);if (user != null) {return true;}return false;
}

我认为我们应该删除这两个单元测试,原因有二:

  • 只要我们正确配置了PersonRepository模拟,我们就知道它的findByEmail()方法是通过使用正确的方法参数调用的。 尽管我们可以将这些测试用例链接到业务需求(用户的电子邮件地址必须是唯一的),但是我们不需要它们来验证此业务需求没有受到损害。
  • 这些单元测试未记录我们服务方法的API。 他们记录了它的实现。 像这样的测试是有害的,因为它们使我们的测试套件变得无关紧要,并且使重构更加困难。

如果我们不配置模拟对象,它们将返回“ nice”值。
Mockito常见问题解答指出:

为了透明和不干扰,默认情况下,所有Mockito模拟都返回“ nice”值。 例如:零,假,空集合或空。 请参阅有关存根的javadocs,以了解确切地返回了默认值。

这就是为什么我们应该始终配置相关的模拟对象的原因! 如果我们不这样做,我们的测试可能就没有用了。

让我们继续清理这个烂摊子。

清理混乱

从测试类中删除这些单元测试后,其源代码如下所示:

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.smith@gmail.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);ArgumentCaptor<User> 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 Answer<User>() {@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);}
}

我们从测试班级中删除了三个单元测试,因此,我们可以享受以下好处:

  • 我们的测试班的单元测试较少 。 这似乎是一个奇怪的好处,因为通常建议我们编写尽可能多的单元测试。 但是,如果考虑到这一点,则减少单元测试是有意义的,因为我们需要维护的测试较少。 这以及每个单元只能测试一件事的事实使我们的代码更易于维护和重构。
  • 我们已经提高了文档的质量 。 删除的单元测试未记录测试服务方法的公共API。 他们记录了它的实施。 由于这些测试已删除,因此更容易弄清测试服务方法的要求。

摘要

这篇博客文章教会了我们三件事:

  • 如果我们无法确定在单元测试失败的情况下受到损害的业务需求,则不应编写该测试。
  • 我们不应该编写没有记录测试方法的公共API的单元测试,因为这些测试使我们的代码(和测试)更加难以维护和重构。
  • 如果发现现有的单元测试违反了这两个规则,则应将其删除。

在本教程中,我们取得了很多成就。 您认为可以使这些单元测试变得更好吗?

如果您想了解有关编写干净测试的更多信息,请阅读我的编写干净测试教程的所有部分 。

翻译自: https://www.javacodegeeks.com/2014/08/writing-clean-tests-to-verify-or-not-to-verify.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/362458.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

http 请求报文和响应报文

1、请求报文 示例&#xff1a; 2、响应报文 示例&#xff1a; 更多专业前端知识&#xff0c;请上 【猿2048】www.mk2048.com

小程序 封装table组件

实在是懒得自己封装了&#xff0c;就在网上找了一下别人封装好的&#xff0c;这里主要参考的是 灰信网上的一篇文章&#xff0c;我在他的基础上改了一下样式&#xff0c;动态生成列表每一列列宽。 表格可左右横向滑动 一、子页面 table.wxml <scroll-view class"ta…

服务器--停止启动服务,查看日志文件

停止启动服务 ./shutdown.sh 停止掉当前服务 ./startup.sh 启动当前服务 例如&#xff1a; Tomcat服务器&#xff1a;在apache-tomcat-7.0.82/bin 目录下&#xff0c;sh shutdown.sh 停止掉当前服务&#xff0c;sh startup.sh 启动当前服务 查看日志文件&#xff1a; Tomcat查看…

linq 解决winForm中控件CheckedListBox操作的问题。(转载)

1.获取CheckedListBox选中项的文本的字符串&#xff0c;并用分隔符连接。系统没有提供相应的属性&#xff0c;方法。 这里我们利用3.0的特性给其来个扩展方法,如下&#xff1a; public static stringGetCheckedItemsText(thisCheckedListBox box) { stringre…

一个在自己的线程中运行测试的JUnit规则

有时&#xff0c;能够在单独的线程中运行JUnit测试会很有帮助。 特别是在编写与封装的ThreadLocal或类似对象交互的集成测试时&#xff0c;这可能会派上用场。 单独的线程将隐式确保每次测试运行都未初始化threadlocal的与线程相关的引用。 这篇文章介绍了提供此类功能的JUnit …

linux LVS管理之keepalive

Keepalive01: ! Configuration File for keepalivedglobal_defs {notification_email { ##接收邮件的地址&#xff1b;就是说通知邮件应该发送给谁&#xff0c;通常写自己的地址rootlocalhost ##收件人&#xff0c;表示发送给本机的root用户}notification_email_from k…

Glass Fish 4.0.1中的Jersey SSE功能

Glass Fish为各种Java EE规范捆绑了不同的参考实现&#xff0c;例如&#xff0c;CDI的Weld&#xff0c;JSF的Mojarra&#xff0c;WebSocket的Tyrus&#xff0c;JAX-RS的Jersey。 Glass Fish 4.0.1即将发布&#xff0c;并计划涵盖许多组件/模块的更新&#xff0c;这些组件/模块当…

canvas1:简单介绍、开始使用、画直线+虚线

目录 一、简单介绍 二、开始使用 三、画直线、虚线 3.1 画直线 1. 画直线的步骤&#xff1a; 2. 设置直线的样式 3. 绘制多条直线 3.2 画虚线 四、整理使用到的方法&#xff1a; 参考了廖雪峰老师的笔记。 一、简单介绍 Canvas是 HTML5新增的组件&#xff0c;它就像…

Test of etco

Test of etco 转载于:https://www.cnblogs.com/yanjunz/archive/2010/12/22/1913852.html

js setTimeout和setInterval区别

1、区别 2、示例代码 <!DOCTYPE html><html lang"zh"><head><meta charset"UTF-8" /><meta name"viewport" content"widthdevice-width, initial-scale1.0" /><meta http-equiv"X-UA-Compatibl…

将Android源码集成到Eclipse中的方法

1、 首先&#xff0c;下载Android源码&#xff1a; Android 2.0源码下载地址&#xff1a;http://download.csdn.net/source/2931366 Android 2.1源码下载地址&#xff1a;http://download.csdn.net/source/2931364 2、 然后&#xff0c;新建一个名为【sources】的文件夹&#x…

为GWT设置开发环境

介绍 这是旨在用Java开发跨平台移动应用程序的系列文章的一部分 。 在此博客文章中&#xff0c;我们将了解GWT是什么&#xff0c;并为GWT设置开发环境。 GWT是一个开源开发工具包&#xff0c;用于开发基于浏览器的复杂Ajax应用程序。 使用GWT&#xff0c;您可以用Java开发Rich…

vue slot的使用

一、自定义组件中多个 slot 很久之前就想把表格封装了&#xff0c;奈何那时太过担心自己的技术。今天趁着劲头大致看了一下&#xff0c;把表格封装了&#xff0c;倒是比想象中的要简单很多 O(∩_∩)O 哈哈~ 暂且不考虑细节&#xff0c;大致封装表格要考虑的有&#xff1a;是否…

虫师Selenium2+Python_6、Selenium IDE

P155——创建测试用例录制脚本编辑脚本定位辅助P159——Selenium IDE 命令在浏览器中打开URL&#xff0c;可以接受相对路径和绝对路径两种形式openopen(url)单击链接、按钮、复选框和单选框click(elementLocator)模拟键盘的输入&#xff0c;向指定的input中输入值type(inputLoc…

flow 静态类型检查 js

1、flow介绍 https://ustbhuangyi.github.io/vue-analysis/prepare/flow.html#为什么用-flow 2、使用 &#xff08;1&#xff09;安装flow &#xff08;2&#xff09;项目目录的test.js文件 类型推断&#xff1a; /*flow*/function split(str) {return str.split( )}split(1…

coreleft函数

函数名: coreleft   功 能: 返回未使用内存的大小   用 法: unsigned coreleft(void);   程序例:   #include <stdio.h>   #include <alloc.h>   int main(void)   {printf("The difference between the highest allocated block and\n");…

Java泛型简介–第6部分

这是对泛型的介绍性讨论的延续&#xff0c; 此处的先前部分可以在此处找到。 在上一篇文章中&#xff0c;我们讨论了关于类型参数的递归边界。 我们看到了递归绑定如何帮助我们重用了车辆比较逻辑。 在该文章的结尾&#xff0c;我建议当我们不够小心时&#xff0c;可能会发生类…

页面监听,一段时间内不操作网页,就自动跳转到登录页

需求&#xff1a;用户在 5 分钟内没有操作网页&#xff0c;就自动跳转到登录页。 环境&#xff1a;jquery 项目&#xff0c;有公共的 js 文件 。 在所有页面都引用的 js 文件中添加下面代码&#xff1a; //判断用户是否在5分钟内未操作页面&#xff0c;如果没有操作&#xff…

类的函数成员的定义无关顺序

类成员变量、成员函数的定义顺序与调用顺序无关如果函数A定义在函数B的后面&#xff0c;但是在函数B也可以直接调用函数A。因为编译器分两步处理类&#xff1a;首先编译成员的声明。然后再编译函数体&#xff08;如果有的话&#xff09;。转载于:https://www.cnblogs.com/AKUN-…

页面刷新 vuex 数据重新被初始化

1、原因 vuex里用来存储的也只是一个全局变量&#xff0c;当页面刷新&#xff0c;该全局变量自然不存在了。 2、解决 使用localStorage存储一份 &#xff08;1&#xff09;storage.js /*** vuex localStorage plugin*/const IS_ALL 0export default function storagePlugi…