如何对我的线程vb.net应用程序进行单元测试?

时间:2015-03-19 14:00:05

标签: .net vb.net multithreading unit-testing visual-studio-2013

我确信一旦测试完成,它会指出一些需要改变的东西才能让所有东西都能用到UI,但基本上我有这个:

frmMain:

Dim totalFinished as Integer = 0
reporter as Func(Of Object) = Function()
                                totalFinished += 1
                                return Nothing
                              End Function
classWithAsync.setReporter(reporter)
classWithAsync.BeginCalculation() ' ignore that this is a private method call
' there is another method I'm leaving out (the public one) which just does
' some set up stuff and then calls BeginCalculation
' Note: this notice isn't actually in my code

ClassWithAsync:

Private Async Sub BeginCalculation()
  ' some logic here
  If synchronous Then
    CalculatorCalculate(calculator)
  Else
    Await calculatorAsyncCalculate(calculator)
  End If 
End Sub

Private Async Function calculatorAsyncCalculate(ByVal calculator as Calculatable) as Hashtable
  Dim result as Hashtable

  result = Await Tasks.Task.Run(Function() as Hashtable
                                 return calculator.Calculate()
                                EndFunction)

  reporter.Invoke()
  return result
End Function

Private Function CalculatorCalculate(ByVal calculator as Calculatable) as Hashtable
  Dim result as Hashtable
  result = calculator.Calculate()

  reporter.Invoke()
  return result
End Function

这里的意图是让记者两次调用。我在同步版本中工作..但是线程测试就像线程甚至没有运行一样(这可能是因为执行正在继续,并且断言在线程完成之前进行评估?)

以下是我的测试:

同步:(通过)

  <TestMethod> Public Sub UpdatesForEachSynchronousProduct() 
    Dim data As Hashtable = TestData.GenerateGroupData("LTD,WDL") 

    CreateMockCalculators(data, privateTarget) 

    privateTarget.SetFieldOrProperty("reporter", reporter) 

    privateTarget.SetFieldOrProperty("synchronous", True) 
    privateTarget.Invoke("BeginCalculation") 

    Assert.AreEqual(2, totalCalculated) 
  End Sub

异步:(不通过)

<TestMethod> Public Sub UpdatesForEachAsyncProduct() 
    Dim data As Hashtable = TestData.GenerateGroupData("LTD,WDL") 

    CreateMockCalculators(data, privateTarget) 

    privateTarget.SetFieldOrProperty("reporter", reporter) 

    privateTarget.SetFieldOrProperty("synchronous", False) 
    privateTarget.Invoke("BeginCalculation") 

    Assert.AreEqual(2, totalCalculated) 
  End Sub 

这个错误是它预期2,但totalCalculated是0。

那么,有没有办法让Assert等到线程完成?

2 个答案:

答案 0 :(得分:0)

想出来。
好吧,我认为某事了 虽然,这种黑客的做法。在这一刻,我不确定它是否仍然是异步的,也许有比我更多异步经验的人可以对此发表评论。我很确定它仍然是Async。当我进一步实施整个项目时,我会更了解。

必须更改BeginCalculation()方法:

  Private Sub BeginCalculation() 
    For Each calculator As Calculatable In calculatables 
      If synchronous Then 
        CalculatorCalculate(calculator) 
      Else 
        ' Call this without Await so that the for each loop 
        ' may continue spawning calculation tasks 
        ' Calling Await halts the execution of this method 
        CalculatorAsyncCalculate(calculator) 
      End If 
    Next 

  End Sub 

之前,我在Await上使用CalculatorAsyncCalculate,这会停止执行循环,直到线程结束 - 这是不受欢迎的。 Visual Studio会抱怨此情况(作为警告)并建议我添加Await并将Async添加到方法标头中。但是......我们可以忽略视觉工作室。

这是我的异步方法:

 Private Async Function CalculatorAsyncCalculate(ByVal calculator As Calculatable) As Tasks.Task(Of Hashtable) 
    Dim result As Hashtable 

    Dim task As New Tasks.Task(Function() As Hashtable 
                                 calculator.Calculate() 

                                 ' Update the reporter 
                                 If Not reporter Is Nothing Then 
                                   reporter.Invoke() 
                                 End If 

                                 Return calculator.GetCalculatedData() 
                               End Function) 

    calculationTasks.Add(task) 

    result = Await Tasks.Task.Run(Function() 
                                    ' Begin calculations 
                                    task.Start() 

                                    ' Wait until the calculations are done 
                                    ' before returning the results 
                                    task.Wait() 

                                    Return calculator.GetCalculatedData() 
                                  End Function) 

    Return result 
  End Function 

我不需要只是异步地运行我需要的东西,而是将代码放入Task对象中,这样我就可以将它添加到跟踪任务的ArrayList中。该数组的目的是让我可以在测试中传递给Threading.Tasks.Task.WaitAll(...)。有点令人费解,但我想它可以更灵活地决定将来如何处理各项任务。

然后我的测试:

<TestMethod> Public Sub UpdatesForEachAsyncProduct() 
    Dim data As Hashtable = TestData.GenerateGroupData("LTD,WDL") 

    CreateMockCalculators(data, privateTarget) 

    privateTarget.SetFieldOrProperty("reporter", reporter) 

    privateTarget.SetFieldOrProperty("synchronous", False) 
    privateTarget.Invoke("BeginCalculation") 

    Dim calcTasks As ArrayList = privateTarget.GetFieldOrProperty("calculationTasks") 
    Dim cTasks(calcTasks.Count - 1) As System.Threading.Tasks.Task 
    Dim array() = calcTasks.ToArray() 
    array.CopyTo(cTasks, 0) 

    Assert.AreEqual(2, cTasks.Count) 

    System.Threading.Tasks.Task.WaitAll(cTasks) 

    Assert.AreEqual(2, totalCalculated) 
  End Sub 

privateTargetPrivateObject(classWithAsync)所以我可以获得私有属性和方法。

在调用wait all之前,我只是确保我有正确数量的任务,以确保BeginCalculation迭代了两个对象,并产生了两个线程。

WaitAll(cTasks)电话在这里是神奇的。没有我失败说Assert预期2但收到1。

答案 1 :(得分:-1)

良好的单元测试的一个基本特征是它应该测试单元;也就是说它应该测试一个单独的,独立的独立功能。多个线程本质上并不是彼此独立的,并且可以以不易重现的方式进行交互以进行测试。

基于此,我认为你应该测试线程之外的实际计算(calculator.Calculate())。

尝试在线程存在的情况下测试计算是试图同时测试太多东西,恕我直言。