c# - 我如何能够对 void 方法进行单元测试?
问题描述
我想测试我的方法来检查车辆的运动方向。它是一种 void 方法,只有一个参数 - 整数。
据我所知,所有的 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;
}
}
}
}
解决方案
从字面上回答你的问题,如果一个方法没有返回值,那么它必须产生一些其他的副作用。(如果它不返回任何东西或有副作用,那么它什么也不做。)
如果方法改变了类本身的状态,您可以执行该方法并断言预期状态:
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());
}
或者您想要测试的行为可能是您的类与某些依赖项的交互。有几种方法可以做到这一点。一种是检查依赖的状态:
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);
}
您还可以使用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());
}
正如您所指出的,为了使其正常工作,有必要将方法分解成更小的块,以便我们可以单独测试。如果一个方法做出很多决定,那么为了完全测试,我们需要测试每个适用的组合,或者代码可以执行的每个可能的分支。这就是为什么具有很多条件的方法更难测试的原因。
我花了一些时间查看您的代码,但还不清楚它实际上在做什么,因此很难建议如何重构它。
您可以采用更大的代码块,这些代码会像这样重复:
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;
}
}
...并用这样的方法替换它们,但您会给它们起清晰、有意义的名称。我不能这样做,因为我不确定他们做了什么:
public void SetEndLaneInDirection(List<LaneIn> laneInList, string direction, bool isEnd)
{
foreach (LaneIn laneIn in laneInList)
{
if (laneIn.Direction == direction)
{
laneIn.EndLane = isEnd;
}
}
}
现在一小段代码更容易测试。或者,如果您有一组方法都进行一些相关更新,如下所示:
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;
然后把它们放在一个方法中,再给它一个清晰的名字。
现在您可以设置类、调用方法并断言您期望的状态。
副作用是,当您用命名良好的方法调用替换代码块时,您的代码变得更容易阅读,因为方法调用用近乎英语的方式说明了每个步骤正在做什么。
事后更难做到。挑战的一部分是学习编写已经更容易测试的代码。好消息是,当我们这样做时,我们的代码自然会变得更简单、更容易理解。我们有测试。
这是一篇关于如何重构现有代码以使其更易于测试的精彩文章。
推荐阅读
- c# - C# 将新窗口重定向到 EasyTabs
- bash - 如何设置 stty -echo 并从 /dev/stdin 读取
- android - Flutter 默认应用程序不运行 - 构建失败 - 任务':app:compressDebugAssets' - 任务:app:processDebugManifest FAILED
- c++ - Eof 在 cin 和 istringstream 的情况下
- python - python - 带有 openpyxl 的 pandas 损坏了我的工作簿
- apache - 无法将 HTTPS 虚拟主机反向映射到原始主机
- firebase - 无法添加用户:[cloud_firestore/unknown] 需要一个类型为“((对象)=> 对象)?”的值,但得到一个类型为“(对象?)=> 动态的值
- r - 匹配行并保持大于等于的值
- python - 在颤振中使用套接字编程将图像发送到服务器
- python - AttributeError:“NoneType”对象没有属性“文本”-BeautifulShop