C# 中的 TDD 测试 service/background 代码
TDD Testing service/background code in C#
因此,我正在编写一个小型异步服务器,每次从网络接收到消息时都会调用委托,但我在如何测试 class 方面遇到了麻烦。我是测试新手,我在 JavaScript 中使用 TDD 完成了第一次测试,现在我正在尝试使用 TDD C#。
我从编写我的第一个测试开始,我想象我想要一些静态工厂来构造对象,这样如果参数无效或为空,我可以在构造对象之前检查它们,所以我写了这个,并做了第一个红绿重构周期。
[TestMethod]
public void CreateShouldCreateAnInstanceWithValidParameters()
{
MessageReceivedCallback callback = delegate(Message message, TcpClient sender) {};
AsyncTcpServer server = AsyncTcpServer.Create(new IPEndPoint(IPAddress.Parse("127.0.0.1"), 40400), callback);
Assert.IsTrue(server != null && server is AsyncTcpServer);
}
我认为到目前为止还不错 (afaik)。
现在,我想我需要一些 Start() 和 Stop() 方法来在后台启动这个服务器,所以它们将是同步方法,首先为一些代码启动一个线程,这些代码将从 TcpListener 读取直到我调用 Stop()。
现在问题来了,因为出现了很多问题,而我似乎没有找到解决问题的最佳解决方案(或只是解决方案):
我测试 Start() 方法的目的是什么?我是否检查它是否创建了一个线程?或者提供服务器的状态会更好吗,以某种 属性 的形式,如 IsRunning 并确保在调用 Start() 之前它的 false,在 Start() 之后是 true,然后在之后Stop() 为假?
万一我测试 IsRunning 值,如果我只实现必要的代码就让它通过(如几本书和教程中所述,只实现使测试通过所需的代码)在调用后更改值,而不创建线程?我认为如果我创建线程,它将是未经测试的代码,因为我找不到正确的方法来测试线程是否已创建(我可以将 class 的线程成员公开为 public, 但我认为那会很丑陋而且不是一个非常封装的 class).
所以,我做错了什么?我该怎么办?
假设您正在尝试遵循 TDD,当您开始编写测试和代码以满足它们时,您的设计可能会发生变化。您会发现很难为其编写测试。发生这种情况可能是因为它真的很难测试,或者它可能表明您尝试做的事情太多了。
in case I test for the IsRunning value, would it be a little bit fake if, I make it pass with just implementing the code necessary
编写尽可能少的代码以使测试通过的目的是确保您不会编写未测试的代码。这有助于鼓励您重构代码以提高设计的可测试性,以便能够编写测试以使代码执行您想要的操作。
看你现在的情况,感觉你在AsyncTcpServer
class上投入的太多了。据我所知,它将负责几件事,包括管理 TCP 连接和 运行 一个单独的执行线程。这是两个截然不同的问题,如果试图在同一个 class 中测试它们,您就会让自己的生活变得艰难。
您需要尝试分离关注点并单独测试它们。因此,例如,使用您可能会创建一个像这样的 ThreadedRunner:
class ThreadedRunner {
public ThreadedRunner(IRunnable runnable) {
// store runnable
}
public void Start() {
// start thread to execute runnable.ThreadFunction
}
public void Stop() {
// Send stop message, block until done
}
}
对于接口 IRunnable:
interface IRunnable {
void ThreadFunction();
void Stop();
}
然后您可以使用 IRunnable 的测试实现来测试 ThreadedRunner。途中还会有其他测试,但最终您可能会得到如下结果:
class TestableRunnable : IRunnable {
public bool IsRunning {get;set;}
public void ThreadFunction() {
// Sleep
// IsRunning=true
// WaitOnShutdownMutex
// IsRunning=false
}
public void Stop() {
// Set shutdown mutex
}
}
TestRunnableStartsThreadAndStops() {
// Create Testable... Pass it to Runner
// Validate testable isn't running
// tell runner to start,
// Validate testable isn't running (it should be sleeping if it's in another thread)
// sleep to give it time to start thread
// validate it's running
// call stop
// validate that it blocked until after the state is stopped
}
然后你可以继续写一个可运行的来处理你的线程处理部分(可能它所做的只是将该进程的不同元素委托给其他 classes,这反过来将具有较小的功能部分,您可以对其进行测试)。
TDD 的一个关键方面是它让您从客户端和测试的角度来审视您的代码,以鼓励您通过重构过程使您的 classes 可用和可测试.
因此,我正在编写一个小型异步服务器,每次从网络接收到消息时都会调用委托,但我在如何测试 class 方面遇到了麻烦。我是测试新手,我在 JavaScript 中使用 TDD 完成了第一次测试,现在我正在尝试使用 TDD C#。
我从编写我的第一个测试开始,我想象我想要一些静态工厂来构造对象,这样如果参数无效或为空,我可以在构造对象之前检查它们,所以我写了这个,并做了第一个红绿重构周期。
[TestMethod]
public void CreateShouldCreateAnInstanceWithValidParameters()
{
MessageReceivedCallback callback = delegate(Message message, TcpClient sender) {};
AsyncTcpServer server = AsyncTcpServer.Create(new IPEndPoint(IPAddress.Parse("127.0.0.1"), 40400), callback);
Assert.IsTrue(server != null && server is AsyncTcpServer);
}
我认为到目前为止还不错 (afaik)。
现在,我想我需要一些 Start() 和 Stop() 方法来在后台启动这个服务器,所以它们将是同步方法,首先为一些代码启动一个线程,这些代码将从 TcpListener 读取直到我调用 Stop()。
现在问题来了,因为出现了很多问题,而我似乎没有找到解决问题的最佳解决方案(或只是解决方案):
我测试 Start() 方法的目的是什么?我是否检查它是否创建了一个线程?或者提供服务器的状态会更好吗,以某种 属性 的形式,如 IsRunning 并确保在调用 Start() 之前它的 false,在 Start() 之后是 true,然后在之后Stop() 为假?
万一我测试 IsRunning 值,如果我只实现必要的代码就让它通过(如几本书和教程中所述,只实现使测试通过所需的代码)在调用后更改值,而不创建线程?我认为如果我创建线程,它将是未经测试的代码,因为我找不到正确的方法来测试线程是否已创建(我可以将 class 的线程成员公开为 public, 但我认为那会很丑陋而且不是一个非常封装的 class).
所以,我做错了什么?我该怎么办?
假设您正在尝试遵循 TDD,当您开始编写测试和代码以满足它们时,您的设计可能会发生变化。您会发现很难为其编写测试。发生这种情况可能是因为它真的很难测试,或者它可能表明您尝试做的事情太多了。
in case I test for the IsRunning value, would it be a little bit fake if, I make it pass with just implementing the code necessary
编写尽可能少的代码以使测试通过的目的是确保您不会编写未测试的代码。这有助于鼓励您重构代码以提高设计的可测试性,以便能够编写测试以使代码执行您想要的操作。
看你现在的情况,感觉你在AsyncTcpServer
class上投入的太多了。据我所知,它将负责几件事,包括管理 TCP 连接和 运行 一个单独的执行线程。这是两个截然不同的问题,如果试图在同一个 class 中测试它们,您就会让自己的生活变得艰难。
您需要尝试分离关注点并单独测试它们。因此,例如,使用您可能会创建一个像这样的 ThreadedRunner:
class ThreadedRunner {
public ThreadedRunner(IRunnable runnable) {
// store runnable
}
public void Start() {
// start thread to execute runnable.ThreadFunction
}
public void Stop() {
// Send stop message, block until done
}
}
对于接口 IRunnable:
interface IRunnable {
void ThreadFunction();
void Stop();
}
然后您可以使用 IRunnable 的测试实现来测试 ThreadedRunner。途中还会有其他测试,但最终您可能会得到如下结果:
class TestableRunnable : IRunnable {
public bool IsRunning {get;set;}
public void ThreadFunction() {
// Sleep
// IsRunning=true
// WaitOnShutdownMutex
// IsRunning=false
}
public void Stop() {
// Set shutdown mutex
}
}
TestRunnableStartsThreadAndStops() {
// Create Testable... Pass it to Runner
// Validate testable isn't running
// tell runner to start,
// Validate testable isn't running (it should be sleeping if it's in another thread)
// sleep to give it time to start thread
// validate it's running
// call stop
// validate that it blocked until after the state is stopped
}
然后你可以继续写一个可运行的来处理你的线程处理部分(可能它所做的只是将该进程的不同元素委托给其他 classes,这反过来将具有较小的功能部分,您可以对其进行测试)。
TDD 的一个关键方面是它让您从客户端和测试的角度来审视您的代码,以鼓励您通过重构过程使您的 classes 可用和可测试.