2015-06-27 24 views
1

因此,我编写了一个小型的异步服务器,每次从网络收到消息时都会调用委托,并且在如何测试该类时遇到问题。我是新来测试,我已经做了我的第一次测试在JavaScript中使用TDD,现在我正在尝试做TDD C#。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()。

现在这里是问题,因为出现很多的问题,我好像还没有找到最佳的解决方案(或只是一个解决方案)的问题:

  • 我怎么测试启动( )方法?我检查它是否创建了一个线程?或者,提供服务器状态是一个更好的主意,以IsRunning之类的属性的形式提供,并确保在Start()被调用为false之后,在Start()为true之后,然后在Stop()之后为假?

  • 如果我测试IsRunning的值,如果我只是通过实现必要的代码来传递它会有点假(正如在几本书和教程中所说的那样,只实现必要的代码测试通行证)在调用完成后要更改的值,而不创建线程?我认为如果我创建线程,它将是未经测试的代码,因为我找不到一个正确的方法来测试线程是否被创建(我可以公开该类的线程成员,但我认为这将是丑陋而不是非常封装的类)。

那么,我做错了什么?我该怎么办?

回答

2

假设您试图遵循TDD,当您开始编写测试和代码以满足它们时,您的设计可能会发生变化。你会发现有些东西很难写出测试。这可能会发生,因为这只是一个非常难以测试的事情,或者它可能表明你试图做太多事情。

的情况下,我测试,将isRunning价值,那会是一点点假,如果,我让它通过与刚刚实施必要

代码

写作的最低金额可能的点,使测试通过,是为了确保你不写代码,你没有测试。这有助于鼓励您重构代码以提高设计的可测试性,以便能够编写测试以使代码执行您所期望的操作。

看着你目前的状况,感觉就像你试图把太多的东西放到你的课堂上。据我所知,它将负责几件事情,包括管理TCP连接和运行单独的执行线程。这是两个不同的关注点,并且通过在同一个班级中尝试对他们进行测试,你会为自己付出沉重的代价。

您需要尝试分离问题并单独进行测试。因此,举例来说,使用您可以创建一个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 
} 

然后可以移动到编写一个可运行的,与你的线程处理部分(这可能是因为它所做的一切将该过程的不同元素委托给其他类,而这些类又将具有较小的功能部分,您可以测试它们)。

TDD的一个关键方面是它可以让您从客户端查看代码并测试透视图,以鼓励您通过重构过程使您的类可用并可测试。

+0

那么,这样用户就不得不实例化每个组件,让服务器从ThreadedRunner到IRunnable的实现,不是吗?我希望用户只知道一个设置整个事物的类,比如AsyncTcpServer,但不是像你所建议的那样持有整个实现。我认为我可以让每个组件都分开,但每个组件都由这个单独的类实例化,因此用户不必担心它的依赖关系。这是可测试的吗?或者可能整个想法不是那么好? –

+1

@AdriánPérez你可以做很多事情来使其更具可测性。通常,注入依赖关系更容易测试,这就是为什么IOC容器(比如ninject)如此流行,还可以通过具有带参数的构造函数,如果注入时使用依赖项,或者默认情况下创建依赖项(如果未实例化) ,或者调用工厂类来构造对象。在封装功能和允许测试之间有一个持续的平衡。通常,如果你沿着tdd路线走,你会得到更多的抽象。 – forsvarir

+0

谢谢@forsvarir,我会看看IOC容器。我也喜欢工厂班的想法。 –