简体   繁体   English

我将如何对void方法进行单元测试?

[英]How would I be able to unit test a void method?

I want to test my method which checks for the direction of movement of vehicles. 我想测试检查车辆运动方向的方法。 It is a void method and has one parameter - integer. 这是一个空方法,具有一个参数-整数。

As far as I know all the if's should be split into different test methods but I am not aware of how to do that. 据我所知,所有的if都应该分为不同的测试方法,但是我不知道该怎么做。

/// <summary>
        /// checks the east neighbour of the crossing on a given cell
        /// </summary>
        /// <param name="cellNr">the cell Nr of the crossing whose neighbour ///its checked</param>

    public void CheckEast(int cellNr)
    {
        Crossing cr = Cells[cellNr].Crossing;

        //If there is east neighbour - laneIns of the current crossing with direction east are NOT endLane
        if (cells[cellNr + 1].Taken)
        {
            foreach (LaneIn laneIn in cr.LaneInList)
            {
                if (laneIn.Direction == "west")
                {
                    laneIn.EndLane = false;
                }
            }

            //car should NOT be spawned from east && LaneOut of current crossing with direction east is NOT endLane
            cr.IncomingStreams[3] = "";
            cr.LaneOutList[0].EndLane = false;

            //LaneIn 'east' of the east neighbour is NOT endLane
            foreach (LaneIn laneIn in cells[cellNr + 1].Crossing.LaneInList)
            {
                if (laneIn.Direction == "east")
                {
                    laneIn.EndLane = false;
                }
            }
            //no spawned car in the neighbour from the east laneIns and LaneOut 'west' is not endlane
            cells[cellNr + 1].Crossing.IncomingStreams[1] = "";
            cells[cellNr + 1].Crossing.LaneOutList[1].EndLane = false;
            cr.Neighbours[1] = cells[cellNr + 1].Crossing;
            cells[cellNr + 1].Crossing.Neighbours[3] = cr;
        }
        //if there is NO neighbour on east side, laneIns of the current crossing are endlane(spawning point)
        //laneout is endlane, cars are erased after leaving it
        else
        {
            cr.Neighbours[1] = null;
            cr.IncomingStreams[3] = "west";
            cr.LaneOutList[0].EndLane = true;
            foreach (LaneIn laneIn in cr.LaneInList)
            {
                if (laneIn.Direction == "west")
                {
                    laneIn.EndLane = true;
                }
            }
        }
    }

To literally answer your question, if a method has no return value then it must produce some other side effect. 要从字面上回答您的问题,如果一个方法没有返回值,那么它还必须产生其他副作用。 (If it doesn't return anything or have a side effect then it doesn't do anything.) (如果它不返回任何东西或没有副作用,那么它什么也不做。)

If method changes the state of the class itself you can execute the method and assert for the expected state: 如果method更改了类本身的状态,则可以执行该方法并声明所需的状态:

public class Counter
{
    public int Value { get; private set; }

    public void Increment() => Value++;
}
public void Counter_increments()
{
    var subject = new Counter();
    subject.Increment();
    Assert.AreEqual(1, subject.Value());
}

Or perhaps the behavior that you want to test is your class's interaction with some dependency. 也许您要测试的行为是类与某些依赖项之间的交互。 There are a few ways to do that. 有几种方法可以做到这一点。 One is to check the state of the dependency: 一种是检查依赖项的状态:

public class ClassThatIncrementsCounter
{
    public readonly Counter _counter;

    public ClassThatIncrementsCounter(Counter counter)
    {
        _counter = counter;
    }

    public void DoSomething()
    {
        // does something and then increments the counter
        _counter.Increment();
    }
}

[TestMethod]
public void DoSomething_increments_counter()
{
    var counter = new Counter();
    var subject = new ClassThatIncrementsCounter(counter);
    subject.DoSomething();
    Assert.AreEqual(1, counter.Value);
}

You can also use a library like Moq to verify that your class interacted with a dependency: 您还可以使用Moq之类的库来验证您的类是否与依赖项进行了交互:

public class ClassThatIncrementsCounter
{
    public readonly ICounter _counter;

    public ClassThatIncrementsCounter(ICounter counter)
    {
        _counter = counter;
    }

    public void DoSomething()
    {
        // does something and then increments the counter
        _counter.Increment();
    }
}

[TestMethod]
public void DoSomething_increments_counter()
{
    var counter = new Mock<ICounter>();

    // indicate that we want to track whether this method was called.
    counter.Setup(x => x.Increment()).Verifiable();
    var subject = new ClassThatIncrementsCounter(counter.Object);
    subject.DoSomething();

    // verify that the method was called.
    counter.Verify(x => x.Increment());
}

In order for this to work well, as you've noted, it's necessary to break methods down into smaller chunks that we can test in isolation. 如前所述,为了使此方法正常工作,有必要将方法分解为较小的块,以便我们可以单独进行测试。 If a method makes lots of decisions then in order to test fully we'd need to test for every applicable combination, or every possible branch along which the code can execute. 如果一个方法做出了很多决定,那么为了进行全面测试,我们需要针对每种适用的组合或代码可以执行的每个可能的分支进行测试。 That's why a method with lots of conditions can be harder to test. 这就是为什么具有很多条件的方法更难测试的原因。

I spent some time looking at your code, but it's not clear enough what it's actually doing to make it easy to suggest how to refactor it. 我花了一些时间查看您的代码,但是还不清楚它实际上在做什么,以至于很难建议如何重构它。

You can take larger chunks of code which get repeated like these: 您可以采用更大的代码块,像这样重复执行:

        foreach (LaneIn laneIn in cr.LaneInList)
        {
            if (laneIn.Direction == "west")
            {
                laneIn.EndLane = false;
            }
        }

        foreach (LaneIn laneIn in cr.LaneInList)
        {
            if (laneIn.Direction == "west")
            {
                laneIn.EndLane = true;
            }
        }

...and replace them with methods like this, with the exception that you would give them clear, meaningful names. ...并用类似的方法替换它们,但您要给它们取清晰,有意义的名称。 I can't do that since I'm not sure what they do: 我不能这样做,因为我不确定他们做什么:

public void SetEndLaneInDirection(List<LaneIn> laneInList, string direction, bool isEnd)
{
    foreach (LaneIn laneIn in laneInList)
    {
        if (laneIn.Direction == direction)
        {
            laneIn.EndLane = isEnd;
        }
    }
}

Now that one smaller piece of code is easier to test. 现在,一小段代码更易于测试。 Or, if you have a block of methods that all make some related update like this: 或者,如果您有全部都进行如下相关更新的方法块:

        cells[cellNr + 1].Crossing.IncomingStreams[1] = "";
        cells[cellNr + 1].Crossing.LaneOutList[1].EndLane = false;
        cr.Neighbours[1] = cells[cellNr + 1].Crossing;
        cells[cellNr + 1].Crossing.Neighbours[3] = cr;

Then put those in a method, and again, give it a clear name. 然后将它们放在方法中,并再次给它起一个清晰的名称。

Now you can set up the class, call the method, and assert what you expect the state to be. 现在,您可以设置类,调用方法并声明您期望的状态。

The side effect is that as you replace chunks of code with well-named method calls, your code becomes easier to read, because the method calls say, in near-English, what each step is doing. 副作用是,当您用命名良好的方法调用替换代码块时,您的代码将变得更易于阅读,因为该方法调用以近似英语的形式说明了每个步骤的操作。

It's much harder to do after the fact. 事后要做起来要困难得多。 Part of the challenge is learning to write code that's already easier to test. 挑战的一部分是学习编写易于测试的代码。 The good news is that as we do so our code naturally becomes simpler and easier to follow. 好消息是,随着我们这样做,我们的代码自然会变得更加简单和易于遵循。 And we have tests. 而且我们有测试。

Here's a great article on how to refactoring existing code to make it easier to test. 这是一篇有关如何重构现有代码以使其更易于测试的出色文章

在你的单元测试,你叫CheckEastcellNr设置为某个值,然后断言预期的副作用(即,改变)是在制造CellsLaneInList等,数据结构。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM