【问题标题】:How to test a void method using JUnit and/or Mockito如何使用 JUnit 和/或 Mockito 测试 void 方法
【发布时间】:2018-11-05 15:26:49
【问题描述】:

提前道歉 - 我知道这已经被问了一千次了,但我浏览了这么多文章/文档,我真是迷路了。

我有一个类,它接收一个 XML 文件,然后使用 DocumentBuilder 将其解析为一个新文件,该文件将用作其他类使用的源。

我需要测试我的方法(这是无效的)。我的项目已经完成,但我需要测试。

如果有人好心告诉我如何做到这一点,我可以继续对我的其他类遵循相同的逻辑,因为我项目中 90% 的方法不会返回任何内容。

谢谢...

 public class XmlToCsv {


    public static void xmlToCsv(String sourceXlsFile, String sourceCsvFile, String sourceXmlFile) throws Exception {

        //define the files
        File stylesheet = new File(sourceXlsFile);
        File xmlSource = new File(sourceXmlFile);

        //create the DocumentBuilder to parse the XML file
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        DocumentBuilder builder = factory.newDocumentBuilder();
        Document document = builder.parse(xmlSource);

        //input the stylesheet to transform the XML to
        StreamSource stylesource = new StreamSource(stylesheet);
        Transformer transformer = TransformerFactory.newInstance().newTransformer(stylesource);

        //write a new output file using the stylesheet format
        Source source = new DOMSource(document);
        Result outputTarget = new StreamResult(new File(sourceCsvFile));
        transformer.transform(source, outputTarget);

    }
}

【问题讨论】:

  • 被测方法不返回任何内容的事实无关紧要。测试是通过查看调用的可观察效果并对其进行断言来执行的。无论是通过返回值还是通过修改其他内容来完成这些效果都会对测试有所改变,但逻辑几乎相同。
  • 这很奇怪。可以发誓测试 void/non-void 方法是两个不同的实体。
  • 不是真的,代码会有些不同,但整体流程是一样的。您不是针对返回值进行断言,而是针对某些副作用(或除此之外)进行断言。这些可能是参数对象的状态、一些全局状态、文件系统或数据库更改,或者其他任何东西,具体取决于方法的作用。返回值只是方法可以做的事情之一,但不是唯一的。
  • 我明白了。那么我对这个类的测试方法是 asserting 正在创建一个文件。这是我的副作用。仍然不确定如何将其格式化为 dbfactory 和流/变压器

标签: java unit-testing testing junit mockito


【解决方案1】:

你试图做的实际上不是做它的方法。您应该只测试 XmlToCsv 类,而不是此类使用的类(DocumentBuilderFactoryDocumentBuilderDocumentStreamSourceTransformerSourceResult)。

现在有 2 种方式可供您选择:干净代码方式或脏测试方式。

最好的解决方案是为你使用的类建立一个依赖框架:

public class XmlToCsv {

    @Inject
    DocumentBuilderFactory factory;

    @Inject
    StreamSource stylesource;

    @Inject
    TransformerFactory transformerFactory;

    public void xmlToCsv(String sourceXlsFile, String sourceCsvFile, String sourceXmlFile) throws Exception {

        //define the files
        File stylesheet = new File(sourceXlsFile);
        File xmlSource = new File(sourceXmlFile);

        //create the DocumentBuilder to parse the XML file
        DocumentBuilder builder = factory.newDocumentBuilder();
        Document document = builder.parse(xmlSource);

        //input the stylesheet to transform the XML to
        StreamSource stylesource = new StreamSource(stylesheet);
        Transformer transformer = transformerFactory.newInstance().newTransformer(stylesource);

        //write a new output file using the stylesheet format
        Source source = new DOMSource(document);
        Result outputTarget = new StreamResult(new File(sourceCsvFile));
        transformer.transform(source, outputTarget);

    }
}

现在可以通过将模拟注入可注入字段来完成测试:

@RunWith(MockitoJUnitRunner.class)
public class XmlToCsvTest {
    @Mock
    DocumentBuilderFactory factory;

    @Mock
    StreamSource style source;

    @Mock
    TransformerFactory transformerFactory;

    @InjectMocks
    XmlToCsv sut; // System Under Test

    @Test
    public void testOk() throws Exception {
        // Mocks
        DocumentBuilder documentBuilder = Mockito.mock(DocumentBuilder.class);
        Document document = Mockito.mock(Document.class);
        // Now you control all objects created in the class and you can test if the right methods are called

        // when-clauses
        Mockito.when(factory.newDocumentBuilder).thenReturn(documentBuilder);
        Mockito.when(documentBuilder.parse(any(File.class)).thenReturn(document);
        // Add all when's here

        // now call the class
        sut.xmlToCsv("", "", "");

        // now verify all calls
        verify(factory, times(1)).newDocumentBuilder();
        verify(documentBuilder, times(1)).parse(any(File.class));
        // etc.
    }
}

肮脏的方式是使用 PowerMockito。使用 PowerMockito,您可以覆盖现有类的新方法。这真的是不得已而为之,我不会推荐它,但是当您无法更改源代码时,您可以使用它。它看起来像这样:

@RunWith(PowerMockRunner.class)
@PrepareForTest({XmlToCsv.class, DocumentBuilderFactory.class})
public class XmlToCsvTest {

    XmlToCsv sut;

    @Test
    public void testXmlToCsv() throws Exception {
        DocumentBuilder documentBuilder = Mockito.mock(DocumentBuilder.class);
        Document document = Mockito.mock(Document.class);

        //when phase
        PowerMockito.mockStatic(DocumentBuilderFactory.newInstance).thenReturn(documentBuilder);
        Mockito.when(factory.newDocumentBuilder).thenReturn(documentBuilder);
        Mockito.when(documentBuilder.parse(any(File.class)).thenReturn(document);

        // now call the class
        sut.xmlToCsv("", "", "");

        //now verify

verify(documentBuilder, times(1)).parse(any(File.class));
    }
}

如您所见,这些示例并不完整,但您会有所不同。

【讨论】:

    【解决方案2】:

    看起来您想要测试此方法的方式是验证写入sourceCsvFile 参数的文件的预期内容,您可以通过在调用您的方法后读取内容来做到这一点。我认为您不需要对 Mockito 做任何事情 - 您的所有参数都是 String 对象,因此无需创建任何模拟。

    【讨论】:

    • 它们是字符串,但它们是应该表示文件路径的字符串。
    • 是的,我传递的参数代表文件的路径(sourceXlsFile、sourceCsvFile、sourceXmlFile)是我在项目中使用的一些源文件。
    • 因为它们是文件的路径,并且正在创建/修改这些文件,所以看起来适当的测试是在测试中调用此方法后打开这些文件并查看内容正确。
    【解决方案3】:

    要测试代码生成器,这是我发现的最佳方法:

    1. 准备一组具有相同 XSL 的 测试用例:对于每个测试用例,一个 XML 输入文件和一个预期的 CSV 输出文件。将输入文件放入一个目录,将预期的文件放入另一个目录,但为每对文件设置相同的名称(case1.xmlcase1.csv)。

    2. 使用私有方法编写一个 JUnit 类,该方法应该进行测试和比较,然后为您要测试的每个案例添加一个 @Test 方法:

      import java.io.File;
      import org.apache.commons.io.FileUtils;
      
      public class XmlToCsvTest
      {
        private final File inputDir=new File("my_input_xml_files");
      
        private final File expectedDir=new File("my_expected_csv_files");
      
        private final File generatedDir=new File("my_generated_files"); // This is just a working dir
      
        private void xmlToCsv(String xslFile, String inputFileName)
        {
          try
          {
            File inputXmlFile=new File(this.inputDir, inputFileName + ".xml");
            File outputCsvFile=new File(this.generatedDir, inputFileName + ".csv");
            File expectedCsvFile=new File(this.expectedDir, inputFileName + ".csv");
            xmlToCsv(xslFile, outputCsvFile.getAbsolutePath(), inputXmlFile.getAbsolutePath());
            FileUtils.contentEquals(expectedCsvFile, outputCsvFile);
          }
          catch (Exception e)
          {
            fail(e.toString());
          }
        }
      
        @Test
        public void xmlToCsvWithCase1()
        {
          xmlToCsv("myFirst.xsl", "case1");
        }
      
        @Test
        public void xmlToCsvWithEmptyFile()
        {
          xmlToCsv("myFirst.xsl", "empty");
        }
      
        @Test
        public void xmlToCsvWithOneRow()
        {
          xmlToCsv("myFirst.xsl", "one-row");
        }
      
          ...
      }
      
    3. 一旦您掌握了这项技术,您就可以通过添加其他具有自己的测试用例的 XSL 来增加测试的复杂性。

    不要忘记将文件集作为资源添加到您的项目中,以成为源代码控制系统的一部分。

    注意:这种方法假设每个输出文件只依赖于输入文件的内容。如果生成器添加了一些独立的内容(如当前日期、当前用户等),则必须进行先前的预处理。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2021-11-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-09-02
      • 1970-01-01
      • 2023-02-13
      相关资源
      最近更新 更多