如何使用TDD方法创建一个符合RESTful标准的Web服务?

3

我被分配了一个任务,使用WCF创建一个具有JSON格式的RESTful Web服务,并使用TDD方法编写以下方法,将产品以文本文件的形式存储在磁盘上:

CreateProduct(Product product)
GetAProduct(int productId) 


URI Templates:
POST to /MyService/Product
GET to /MyService/Product/{productId}

创建服务及其Web方法很容易,但是如何用TDD方法处理这个任务呢?在创建SUT代码之前,您应该先创建一个测试。
单元测试的规则要求它们也应该是独立和可重复的。
以下是我的一些困惑和问题:
1)我应该通过添加对实际服务实现的引用或针对服务的URL编写我的单元测试(在这种情况下,我必须托管并运行服务)?还是两者都有?
2)我认为可以采取一种方法,即只创建一个测试方法,在其中创建一个产品,调用CreateProduct() 方法,然后调用GetAProduct()方法,并断言发送的产品与接收到的产品相同。在 TearDown() 事件中,只需删除已创建的产品。
但是我对上述内容有以下问题:
- 它测试的功能超过了一个,因此它不是真正的单元测试。 - 它没有检查数据是否正确存储在文件中。 - 这是TDD吗?
如果我为每个Web方法创建单独的单元测试,例如调用GetAProduct() Web方法,则必须在服务器上物理存储一些测试数据,因为它不能依赖于CreateProduct() 单元测试。 它们应该能够独立运行。
请给出建议。
谢谢,

5
在我看来,获取URL并解析其结果已超出了单元测试的范畴。单元测试应该在应用程序的类和方法级别上操作,而不是基础设施级别上。您还要避免在测试中使用“真实”的存储,因为那样您就不仅测试使用数据库的代码,还测试数据库本身。您应该了解一下模拟(Mocking),有很多相关信息(例如,在SO上)。 - Niklas B.
1
谢谢 Niklas。你说的很有道理,但是如果我不测试 URL,如何测试服务是否接受了 JSON 格式或序列化是否正确呢?在测试 URL 时,必须使用真实存储。我想我需要编写一些单元测试和集成测试的组合吧?那 TDD 要怎么说呢? - The Light
我不明白TDD与此有何关系。它只是说在某个迭代过程中应该先编写测试,然后再进行实现。至于你的问题,单元测试将确保(1)你的JSON解析器正常工作(2)序列化正常工作(3)你的服务正在向底层传输正确的数据(你可以使用存根或模拟对象来检查与你的解析器/序列化器/任何其他实现相同接口的东西)。你还会测试诸如URL解析之类的东西,但作为一个独立的模块。除此之外的一切都是集成测试。 - Niklas B.
1
因此,使用单元测试只测试隔离的组件。而使用集成测试则测试组件如何协同工作。 - Niklas B.
那么我应该从哪个测试开始,怎么做呢?在TDD中,我只需要编写单元测试而不是集成测试吗? - The Light
@TheLight 我会从低级别的数据集成测试开始,然后迅速转向单元测试。我会最后测试WCF(端点、JSON等)。 - maxbeaudoin
2个回答

1
我建议不要担心Web服务端点,而是关注系统的行为。为了讨论方便,我将放弃所有技术术语,谈谈我认为您试图解决的核心业务问题:创建产品目录。
为了做到这一点,首先要考虑产品目录的作用,而不是如何实现它的技术细节。以此作为测试的起点。
public class ProductCatalogTest
{
    [Test]
    public void allowsNewProductsToBeAdded() {}

    [Test]
    public void allowsUpdatesToExistingProducts() {}

    [Test]
    public void allowsFindingSpecificProductsUsingSku () {}
}

我不会在这里详细介绍如何实现测试和生产代码,但这是一个起点。一旦你完成了ProductCatalog生产类的工作,你可以把注意力转向技术细节,比如制作Web服务和编排你的JSON。

我不是.NET专家,所以这将主要是伪代码,但它可能最终看起来像这样。

public class ProductCatalogServiceTest
{
   [Test]
   public void acceptsSkuAsParameterOnGetRequest()
   {
       var mockCatalog = new MockProductCatalog(); // Hand rolled mock here.
       var catalogService = new ProductCatalogService(mockCatalog);

       catalogService.find("some-sku-from-url")

       mockCatalog.assertFindWasCalledWith("some-sku-from-url");
   }

   [Test]
   public void returnsJsonFromGetRequest()
   {
       var mockCatalog = new MockProductCatalog(); // Hand rolled mock here.
       mockCatalog.findShouldReturn(new Product("some-sku-from-url"));
       var mockResponse = new MockHttpResponse(); // Hand rolled mock here.

       var catalogService = new ProductCatalogService(mockCatalog, mockResponse);

       catalogService.find("some-sku-from-url")

       mockCatalog.assertWriteWasCalledWith("{ 'sku': 'some-sku-from-url' }");
   }
}

你现在已经进行了端到端的测试,并且测试了整个过程。我个人会测试包含在 ProductCatalog 中的业务逻辑,很可能跳过测试编组,因为这些很可能都由框架完成,而将控制器连接到产品目录只需要少量代码。你的结果可能会有所不同。

最后,在测试驱动目录时,我期望代码被拆分成多个类,那里涉及到模拟,因此它们将进行单元测试,而不是大型集成测试。同样地,这是另一天的话题。

希望这能帮到你!

Brandon


0

好的,回答你的问题,我的做法是编写测试调用REST服务并使用类似Rhino Mocks的工具进行安排(即设置对调用的期望),执行(实际运行调用要测试的单元,并断言您得到了预期的返回结果)。您可以模拟出REST调用的预期结果。从前端到后端的实际REST服务测试是集成测试而不是单元测试。

所以更清楚地说,您需要编写的单元测试是围绕实际调用业务逻辑中的REST Web服务的测试...

就像这是您建议的实现方式(假装这甚至还没有被编写)

public class SomeClass
    {
        private IWebServiceProxy proxy;
        public SomeClass(IWebServiceProxy proxy)
        {
            this.proxy = proxy;
        }

        public void PostTheProduct()
        {
            proxy.Post("/MyService/Product");
        }

        public void REstGetCall()
        {
            proxy.Get("/MyService/Product/{productId}");
        }
    }

这是你可能考虑编写的测试之一。

[TestFixture]
    public class TestingOurCalls()
    {
        [Test]
        public Void TestTheProductCall()  
        {    
        var webServiceProxy = MockRepository.GenerateMock<IWebServiceProxy>();
        SomeClass someClass = new SomeClass(webServiceProxy);

        webServiceProxy.Expect(p=>p.Post("/MyService/Product"));

        someClass.PostTheProduct(Arg<string>.Is.Anything());

        webServiceProxy.VerifyAllExpectations();

       }

}


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接