单元测试 - 如何测试长时间运行的外部进程

时间:2017-07-25 17:54:11

标签: c# unit-testing

我有一个方法从另一个类调用另一个方法,该方法又在第二个类中执行许多私有方法。一种方法写入一个文件然后由外部进程拾取,外部进程运行5到30分钟,具体取决于处理的需要,然后生成另一个文件,由我的应用程序读取,然后将其读取并返回到被称为初始方法。

我知道我描述的不是"单位"但是这个方法是公开的,所以我的问题是我需要在这个方法中测试什么?如何在第二个类中模拟对方法的调用?或者我只是让方法正常运行,无论是5分钟还是30分钟?

class A
{
    public List<DataClass> MethodUnderTest()
    {
        List<string> requiredData;
        SecondClass B = New SecondClass();
        requiredData = B.GenerateFile();
        //B.GenerateFile() Executes a number of private methods within the SecondClass, 
        //This can be treated as a service call.  This runs between 5 and 30 mins
        return requiredData.Select(r => new DataClass{
                                     Property1 = r.Substring(0,2),
                                     Property2 = r.Substring(3,5),
                                     Property3 = r.Substring(9,10)
                                     }).ToList();
    }
}

3 个答案:

答案 0 :(得分:1)

我认为你回答了自己的问题,但我将概述我会做什么:为SecondClass创建一个接口并依赖于它(你需要注入那个依赖(你喜欢的任何方式)),然后我会嘲笑它在单元测试中的行为。因为moq会做这个工作。这给我们带来了什么样的逻辑来测试:唯一有趣的细节是子串的东西 - 所以测试一下。然后,您将需要访问SecondClass并查看您可以在那里创建的单元测试。

单位和非单位测试怎么样?我说你需要两者,但你会不同地执行它们(每次更改机器上的代码时都应运行单元(因此,它们需要快速执行(因此模拟是关键),如ms中的几k),以及将在提交时执行所有依赖项的集成/功能测试(通常在构建服务器上)。并且它们将花费更多时间(在您的情况下至少30秒到几米)但这很好,因为它们不经常运行

答案 1 :(得分:1)

SecondClass视为第三方依赖,并将其封装在您控制的代码之后。从依赖项创建所需功能的抽象;

public interface ISecondClass {
    List<string> GenerateFile();
}

A类也有太多顾虑,应该删除任何不属于A类的责任/顾虑。

public interface IDataClassParser {
    DataClass Parse(string data);
}

public class DefaultDataClassParser : IDataClassParser {
    public DataClass Parse(string data) {
        return new DataClass {
            Property1 = data.Substring(0, 2),
            Property2 = data.Substring(3, 5),
            Property3 = data.Substring(9, 10)
        };
    }
}

以上是用于演示目的的简单示例。

将目标类重构为现在显式依赖于抽象,而不是结果。

public class A {
    private readonly ISecondClass B;
    private readonly IDataClassParser parser;

    public A(ISecondClass B, IDataClassParser parser) {
        this.B = B;
        this.parser = parser;
    }

    public List<DataClass> MethodUnderTest() {
        List<string> requiredData = B.GenerateFile();
        return requiredData.Select(createNewDataClass).ToList();
    }

    private DataClass createNewDataClass(string r) {
        return parser.Parse(r);
    }
}

A不再与实现问题紧密耦合,现在可以单独测试被测方法。

示例测试

[TestClass]
public class ATest {
    [TestMethod]
    public void MethodUnderTest_Should_Return_DataClassList() {
        //Arrange
        List<string> mockData = new List<string>();
        //TODO: Populate mockData
        var mockB = new Mock<ISecondClass>();
        mockB.Setup(_ => _.GenerateFile()).Returns(mockData);

        var sut = new A(mockB.Object, new DefaultDataClassParser());

        //Act
        var actual = sut.MethodUnderTest();

        //Assert
        //TODO: assert that the actual result satisfies expectations
    }        
}

从技术上讲,上面现在只是实际测试解析器,因此可以编写一个额外的测试来单独测试解析代码。

[TestClass]
public class DataClassParserTest {
    [TestMethod]
    public void DataClassParser_Should_Return_DataClass() {
        //Arrange
        string mockData = "..."; //TODO: Populate mockData
        var sut = new DefaultDataClassParser();

        //Act
        var actual = sut.Parse(mockData);

        //Assert
        //TODO: assert that the actual result satisfies expectations
    }        
}

最后,在生产中,长期运行的类的实现将来自抽象并封装依赖SecondClass

public class SecondClassWrapper : ISecondClass {
    private SecondClass B = new SecondClass();
    public List<string> GenerateFile() {
        return B.GenerateFile();
    }
}

答案 2 :(得分:1)

您正在尝试确定如何对调用另一个类(A)的方法的类(SecondClass)的方法进行单元测试。您正在测试的方法并不多。在大多数情况下,如果它调用的方法有效,它将起作用。因此(GenerateFile)是专注于编写测试的最佳位置。

您提到SecondClass会调用许多私有方法。这些方法有多复杂?我猜测(我可能会离开)其中一些相当复杂,因为调用它们的方法需要很长时间才能运行。如果测试构成该长期运行过程的小任务,则您的过程是经过测试的方法或类的组合。

如果这些私有方法很复杂,那么您无法通过测试调用调用这些方法的方法的方法来测试它们。也许这些私有方法的某些行为可以放在单独的类中,这些类本身可以进行单元测试。

听起来好像很多东西,但为了获得测试的好处,我们必须编写可以测试的代码。这改变了我们编写代码的方式,通常情况下会更好。

回到您最初询问的方法:
您有一个方法需要List<string>并将其转换为List<DataClass>。为简单起见,为什么不编写一个将string转换为DataClass的方法?也许你可以把它放在一个扩展中,甚至可以放在它自己的类中。

DataClass FromString(string input)
{
    return new DataClass{
        Property1 = r.Substring(0,2),
        Property2 = r.Substring(3,5),
        Property3 = r.Substring(9,10)
    }
}

然后,您可以测试该方法,以确保从字符串解析的DataClass具有预期的属性值。而你之前的陈述变得如此简单,甚至几乎不需要对其自身进行测试。

return requiredData.Select(r => FromString(r)).ToList();

正如另一个答案中所建议的那样,SecondClass理想情况下应该是A所依赖的界面。但即使它不是,如果SecondClass本身已经过单元测试,那么MethodUnderTest将是合理的,因为它除了调用一个测试方法并将结果传递给另一种测试方法。

但是如果你用接口取代SecondClass,那么MethodUnderTest变得非常容易测试,因为你只需要使用&#34; test double&#34;这是一个简单的实现,仅用于测试目的& #39; s硬编码返回您要测试的值。因此,您的测试有效地说,&#34;假设ISecondClass返回这些值,我希望MethodUnderTest返回这些值。&#34;它不会花费几分钟来运行,只需几毫秒。

即使所有部分都经过测试,您仍然需要进行端到端的整个测试的集成测试。你能创建一个包含较少数据的小文件并对其进行测试吗?