Java模拟:如何自动化Java单元测试,包括模拟和断言

Java中的模拟是什么?只需单击一下按钮,即可自动生成单元测试,包括所有模拟和验证。
好的单元测试是确保您的代码在今天能正常工作,并在将来继续有效的好方法。全面的测试套件具有良好的基于代码和基于行为的覆盖范围,可以为组织节省大量时间和麻烦。但是,看到项目编写的测试不够多的情况并不少见。实际上,一些开发人员甚至一直在完全反对使用它们。

Java模拟:如何自动化Java单元测试,包括模拟和断言

Java中的模拟是什么需单击一下按钮,即可自动生成单元测试,包括所有模拟和验证。

好的单元测试是确保您的代码在今天能正常工作,并在将来继续有效的好方法。全面的测试套件具有良好的基于代码和基于行为的覆盖范围,可以为组织节省大量时间和麻烦。但是,看到项目编写的测试不够多的情况并不少见。实际上,一些开发人员甚至一直在完全反对使用它们。

什么是好的单元测试/h4>

开发人员未编写足够的单元测试的原因有很多。最大的原因之一是它们需要花费大量的时间来构建和维护,尤其是在大型、复杂的项目中。在复杂的项目中,单元测试通常需要实例化和配置许多对象。这需要花费很多时间来设置,并且可能使测试本身比所测试的代码复杂(或更复杂)。

让我们看一下Java中的示例:

public LoanResponse requestLoan(LoanRequest loanRequest, LoanStrategy strategy){     LoanResponse response = new LoanResponse();response.setApproved(true);if (loanRequest.getDownPayment().compareTo(loanRequest.getAvailableFunds()) > 0){        response.setApproved(false);response.setMessage("error.insufficient.funds.for.down.payment"); return response;     }     if (strategy.getQualifier(loanRequest)< strategy.getThreshold(adminManager)) {         response.setApproved(false);  response.setMessage(getErrorMessage());     }     return response; }

在这里,我们有一个处理LoanRequest并生成LoanResponse的方法。请注意LoanStrategy参数,该参数用于处理LoanRequest。策略对象可能很复杂——它可能访问数据库、外部系统或引发RuntimeException。要为requestLoan()编写测试,我需要担心要测试使用哪种类型的LoanStrategy,并且可能需要使用各种LoanStrategy实现和LoanRequest配置来测试我的方法。

forrequestLoan()的单元测试可能如下所示:

@Test public void testRequestLoan() throws Throwable {    // Set up objects DownPaymentLoanProcessor processor = new DownPaymentLoanProcessor(); LoanRequest loanRequest = LoanRequestFactory.create(1000, 100, 10000); LoanStrategy strategy = new AvailableFundsLoanStrategy(); AdminManager adminManager = new AdminManagerImpl(); underTest.setAdminManager(adminManager); Map<String, String> parameters = new HashMap<>(); parameters.put("loanProcessorThreshold", "20"); AdminDao adminDao = new InMemoryAdminDao(parameters); adminManager.setAdminDao(adminDao); // Call the method under test    LoanResponse response = processor.requestLoan(loanRequest, strategy); // Assertions and other validations } 

如您所见,我的测试中有一整段内容只是创建对象和配置参数。查看requestLoan()方法并不明显,需要设置哪些对象和参数。为了创建此示例,我必须运行测试,添加一些配置,然后再次运行并一遍又一遍地重复该过程。我不得不花太多时间弄清楚如何配置AdminManagerLoanStrategy,而不是专注于我的方法以及在那里需要测试的内容。而且,我仍然需要扩展测试以涵盖AdminDao的更多LoanRequest案例、更多策略和更多参数。

另外,通过使用真实的对象进行测试,我的测试实际上不仅验证了requestLoan()的行为——还取决于AvailableFundsLoanStrategyAdminManagerImplAdminDao的行为才能运行我的测试。实际上,我也在测试这些类。在某些情况下,这是理想的,但在其他情况下则不是。另外,如果其他类之一发生更改,即使requestLoan()的行为未更改,测试也可能开始失败。对于此测试,我们宁愿将被测类与其依赖项隔离。

Java中的模拟是什么/span>

解决复杂性问题的一种方法是模拟那些复杂的对象。对于此示例,我将从对LoanStrategy参数使用模拟开始:

@Testpublic void testRequestLoan() throws Throwable{    // Set up objects    DownPaymentLoanProcessor processor = new DownPaymentLoanProcessor();    LoanRequest loanRequest = LoanRequestFactory.create(1000, 100, 10000);    LoanStrategy strategy = Mockito.mock(LoanStrategy.class);Mockito.when(strategy.getQualifier(any(LoanRequest.class))).thenReturn(20.0d);Mockito.when(strategy.getThreshold(any(AdminManager.class))).thenReturn(20.0d);    // Call the method under test    LoanResponse response = processor.requestLoan(loanRequest, strategy);    // Assertions and other validations}

让我们看看这里发生了什么。我们使用Mockito.mock()创建一个LoanStrategy的模拟实例。因为我们知道该策略将调用getQualifier()getThreshold(),所以我们使用Mockito.when().thenReturn()定义了这些调用的返回值。对于此测试,我们不在乎LoanRequest实例的值是什么,也不再需要真正的AdminManager,因为AdminManager仅由真正的LoanStrategy使用。

此外,由于我们没有使用真正的LoanStrategy,所以我们不在乎LoanStrategy的具体实现会做什么。我们不需要设置测试环境,依赖项或复杂的对象。我们专注于测试requestLoan(),而不是LoanStrategyAdminManager。被测方法的代码流直接由模拟程序控制。

使用Mockito编写此测试要比创建一个复杂的LoanStrategy实例要容易得多。但是仍然存在一些挑战:

  • 对于复杂的应用程序,测试可能需要大量的模拟
  • 如果您不熟悉Mockito,则需要学习其语法和模式
  • 您可能不知道需要模拟哪些方法
  • 当应用程序更改时,测试(和模拟)也需要更新


使用Java单元测试生成器解决模拟挑战

我们使用Parasoft Jtest来帮助解决上述挑战。单元测试模块Parasoft Jtest是用于Java测试的企业解决方案,可帮助开发人员管理Java软件开发的风险。

在单元测试方面,Parasoft Jtest可帮助您自动化使用模拟创建和维护单元测试中最困难的部分。对于上面的示例,它可以通过单击一次按钮自动为requestLoan()生成测试,包括您在示例测试中看到的所有模拟和验证。

Java模拟:如何自动化Java单元测试,包括模拟和断言

在这里,我使用了Parasoft Jtest单元测试助手工具栏中的“常规”操作来生成以下测试:

@Testpublic void testRequestLoan() throws Throwable{  // Given     DownPaymentLoanProcessor underTest = new DownPaymentLoanProcessor();// When double availableFunds = 0.0d;// UTA: default value double downPayment = 0.0d;// UTA: default value double loanAmount = 0.0d;// UTA: default value     LoanRequest loanRequest = LoanRequestFactory.create(availableFunds, downPayment, loanAmount);LoanStrategy strategy = mockLoanStrategy();LoanResponse result = underTest.requestLoan(loanRequest, strategy); // Then    // assertNotNull(result);}

此测试的所有模拟都在辅助方法中进行:

private static LoanStrategy mockLoanStrategy() throws Throwable{    LoanStrategy strategy = mock(LoanStrategy.class);    double getQualifierResult = 0.0d; // UTA: default value    when(strategy.getQualifier(any(LoanRequest.class))).thenReturn(getQualifierResult);    double getThresholdResult = 0.0d; // UTA: default value    when(strategy.getThreshold(any(AdminManager.class))).thenReturn(getThresholdResult);    return strategy;}

为我设置了所有必需的模拟——Parasoft Jtest检测到对getQualifier()getThreshold()的方法调用,并模拟了这些方法。一旦在单元测试中为availableFundsdownPayment等配置了值,就可以运行测试了(我也可以生成参数化测试以更好地覆盖!)。另请注意,该助手会通过其注释“UTA:默认值”为更改哪些值提供一些指导,从而使测试更加容易。

这样可以节省生成测试的大量时间,尤其是在我不知道需要模拟什么或如何使用Mockito API的情况下。

处理代码更改

当应用程序逻辑更改时,测试通常也需要更改。如果测试写得很好,则在不更新测试的情况下更新代码将导致测试失败。通常,更新测试中的最大挑战是了解需要更新的内容以及如何准确执行该更新。如果存在大量的模拟和值,则可能很难找到必要的更改。

为了说明这一点,让我们对测试中的代码进行一些更改:

public LoanResponse requestLoan(LoanRequest loanRequest, LoanStrategy strategy){  ...    String result = strategy.validate(loanRequest);    if (result != null && !result.isEmpty()) {        response.setApproved(false);        response.setMessage(result);        return response;    }  ...    return response;}

我们向LoanStrategy添加了一个新方法validate(),现在可以从requestLoan()调用它。可能需要更新测试以指定validate()应该返回什么。

在不更改生成的测试的情况下,让我们在Parasoft Jtest单元测试助手中运行它:

Java模拟:如何自动化Java单元测试,包括模拟和断言

在我的测试运行期间,Parasoft Jtest检测到在模拟的LoanStrategy参数上调用了validate()。由于尚未为模拟设置方法,因此助手建议我模拟validate()方法。“模拟”快速修复操作会自动更新测试。这是一个简单的示例,但是对于复杂的代码,很难找到丢失的模拟,建议和快速修复可以为我们节省大量调试时间。

使用快速修复更新测试后,我可以看到新的模拟并为validateResult设置所需的值:

private static LoanStrategy mockLoanStrategy() throws Throwable {    LoanStrategy strategy = mock(LoanStrategy.class);    String validateResult = ""; // UTA: default value  when(strategy.validate(any(LoanRequest.class))).thenReturn(validateResult);    double getQualifierResult = 20.0d; when(strategy.getQualifier(any(LoanRequest.class))).thenReturn(getQualifierResult);    double getThresholdResult = 20.0d;  when(strategy.getThreshold(any(AdminManager.class))).thenReturn(getThresholdResult);    return strategy;}

我可以使用一个非空值配置validateResult,以测试该方法输入新代码块的用例,或者可以使用空值(或null)来验证未输入新块时的行为。

分析测试流程

助手还提供了一些有用的工具来分析测试流程。例如,这是我们测试运行的流程树:

Java模拟:如何自动化Java单元测试,包括模拟和断言

Parasoft Jtest单元测试助手的流程树,显示在测试执行期间进行的调用

运行测试时,我可以看到测试为LoanStrategy创建了一个新的模拟,并模拟了validate()getQualifier()getThreshold()方法。我可以选择方法调用,并查看(在“变量”视图中)向该调用发送了哪些参数,以及返回了什么值(或抛出了异常)。在调试测试时,这比挖掘日志文件更容易使用和理解。

总结

自此,您可以自动化单元测试的许多方面。Parasoft Jtest可帮助您以更少的时间和精力来生成单元测试,从而帮助您降低与模拟相关的复杂性。它还提出了许多其他建议来改进基于运行时数据的现有测试,并支持参数化测试,Spring Application测试和PowerMock(用于模拟静态方法和构造函数)。如果您想要在自己的环境中进行试用,可以在这里获取免费试用

Java模拟:如何自动化Java单元测试,包括模拟和断言

标签:

声明:本站部分文章及图片源自用户投稿,如本站任何资料有侵权请您尽早请联系jinwei@zod.com.cn进行处理,非常感谢!

上一篇 2020年11月7日
下一篇 2020年11月7日

相关推荐

发表回复

登录后才能评论