我们有一个与ServiceStack连接的ASP.NET Web应用程序.我以前从来没有编写过功能测试,但是已经完成了对我们的API编写测试(nUnit)的任务,并证明它一直运行到数据库级别.

有人可以帮我开始写这些测试吗?

以下是我们的用户服务的一个post方法的例子.

public object Post( UserRequest request )
{
    var response = new UserResponse { User = _userService.Save( request ) };

    return new HttpResult( response )
    {
        StatusCode = HttpStatusCode.Created,Headers = { { HttpHeaders.Location,base.Request.AbsoluteUri.CombineWith( response.User.Id.ToString () ) } }
    };
}

现在我知道如何写一个标准的单元测试,但在这一部分我感到困惑.我必须通过HTTP调用WebAPI并初始化一篇文章?我只是像我单位测试一样调用方法吗?我认为这是“功能测试”的一部分,不包括我.

解决方法

测试服务合同

对于端到端功能测试,我着重于验证服务是否可以接受请求消息,并为简单的用例产生预期的响应消息.

Web服务是一种合同:给定某种形式的消息,该服务将产生给定形式的响应消息.第二,服务将以某种方式改变其基础系统的状态.请注意,对于最终客户端,消息不是您的DTO类,而是以特定动词发送给特定URL的给定文本格式(JSON,XML等)的请求的特定示例,其中给定集合的标题.

ServiceStack Web服务有多个层次:

client -> message -> web server -> ServiceStack host -> service class -> business logic

简单的单元测试和集成测试最适合业务逻辑层.直接对您的服务类进行简单的写入单元测试也很容易:构造一个DTO对象应该很简单,在服务类上调用Get / Post方法,并验证响应对象.但是,这些测试不会测试ServiceStack主机中发生的任何事情:路由,序列化/反序列化,请求过滤器的执行等.当然,您不想测试ServiceStack代码本身,因为该框架代码具有自己的单元测试.但是,有机会测试特定请求消息进入服务并从中出来的具体路径.这是服务合同的一部分,无法通过直接查看服务类来完全验证.

不要尝试100%的覆盖率

我不建议尝试通过这些功能测试来覆盖所有业务逻辑的100%.我专注于通过这些测试来覆盖主要用例 – 通常每个端点有一个或两个需求示例.通过针对业务逻辑类编写传统的单元测试,对特定业务逻辑案例的详细测试更有效率. (您的业务逻辑和数据访问未在ServiceStack服务类中实现,对吗?)

实施

我们将在运行ServiceStack服务,并使用HTTP客户端向其发送请求,然后验证响应的内容.此实现特定于NUnit;在其他框架中也应该有类似的实现.

首先,您需要一个NUnit安装夹具,在所有测试之前运行一个,以设置进程中的ServiceStack主机:

// this needs to be in the root namespace of your functional tests
public class ServiceStackTestHostContext
{
    [TestFixtureSetUp] // this method will run once before all other unit tests
    public void OnTestFixtureSetUp()
    {
        AppHost = new ServiceTestAppHost();
        AppHost.Init();
        AppHost.Start(ServiceTestAppHost.BaseUrl);
        // do any other setup. I have some code here to initialize a database context,etc.
    }

    [TestFixtureTearDown] // runs once after all other unit tests
    public void OnTestFixtureTearDown()
    {
        AppHost.dispose();
    }
}

您的实际ServiceStack实现可能有一个AppHost类,它是AppHostBase的一个子类(至少在IIS中运行).我们需要对不同的基类进行子类化,以便在进程中运行此ServiceStack主机:

// the main detail is that this uses a different base class
public class ServiceTestAppHost : AppHostHttpListenerBase
{
    public const string BaseUrl = "http://localhost:8082/";

    public override void Configure(Container container)
    {
        // Add some request/response filters to set up the correct database
        // connection for the integration test database (may not be necessary
        // depending on your implementation)
        RequestFilters.Add((httpRequest,httpResponse,requestDto) =>
        {
            var dbContext = MakeSomeDatabaseContext();
            httpRequest.Items["DatabaseIntegrationTestContext"] = dbContext;
        });
        ResponseFilters.Add((httpRequest,responseDto) =>
        {
            var dbContext = httpRequest.Items["DatabaseIntegrationTestContext"] as DbContext;
            if (dbContext != null) {
                dbContext.dispose();
                httpRequest.Items.Remove("DatabaseIntegrationTestContext");
            }
        });

        // Now include any configuration you want to share between this 
        // and your regular AppHost,e.g. IoC setup,EndpointHostConfig,// JsConfig setup,adding Plugins,etc.
        SharedAppHost.Configure(container);
    }
}

现在你应该有一个正在运行的所有测试的ServiceStack服务.发送请求到这个服务现在很容易:

[Test]
public void Mytest()
{
    // first do any necessary database setup. Or you Could have a
    // test be a whole end-to-end use case where you do Post/Put 
    // requests to create a resource,Get requests to query the 
    // resource,and Delete request to delete it.

    // I use RestSharp as a way to test the request/response 
    // a little more independently from the ServiceStack framework.
    // Alternatively you Could a ServiceStack client like JsonServiceClient.
    var client = new RestClient(ServiceTestAppHost.BaseUrl);
    client.Authenticator = new HttpBasicAuthenticator(NUnitTestLoginName,NUnitTestLoginPassword);
    var request = new RestRequest...
    var response = client.Execute<ResponseClass>(request);

    // do assertions on the response object Now
}

请注意,您可能必须以管理员模式运行Visual Studio才能使服务成功打开该端口;见下面的评论和this follow-up question.

进一步:模式验证

我在一个企业系统的API上工作,在那里客户为自定义解决方案付出了大量的代价,并期待着一个非常强大的服务.因此,我们使用模式验证来绝对确定我们不会在最低级别打破服务合同.我不认为模式验证对于大多数项目是必要的,但是如果您想进一步测试一下,您可以执行以下操作.

您可以无意中破坏您的服务合同的方式之一是以不向后兼容的方式更改DTO:例如,重命名现有属性或更改自定义序列化代码.这可以通过使数据不再可用或可解析来破坏您的服务的客户端,但您通常无法通过对业务逻辑进行单元测试来检测此更改.防止这种情况发生的最好方法是keep your request DTOs separate and single-purpose and separate from your business/data access layer,但是有一个机会有人会意外地错误地应用重构.

为了防范这种情况,您可以在功能测试中添加模式验证.我们只针对具体的使用案例,我们知道付费客户端实际上将用于生产.这个想法是,如果这个测试中断了,那么我们知道打破测试的代码如果要部署到生产中,就会打破这个客户端的集成.

[Test(Description = "Ticket # where you implemented the use case the client is paying for")]
public void MySchemaValidationtest()
{
    // Send a raw request with a hard-coded URL and request body.
    // Use a non-ServiceStack client for this.
    var request = new RestRequest("/service/endpoint/url",Method.POST);
    request.RequestFormat = DataFormat.Json;
    request.AddBody(requestBodyObject);
    var response = Client.Execute(request);
    Assert.That(response.StatusCode,Is.EqualTo(HttpStatusCode.OK));
    RestSchemaValidator.ValidateResponse("ExpectedResponse.json",response.Content);
}

要验证响应,请创建一个描述响应的预期格式的JSON Schema文件:该特定用例需要存在哪些字段,预期有哪些数据类型等.此实现使用Json.NET schema parser.

using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Schema;

public static class RestSchemaValidator
{
    static readonly string ResourceLocation = typeof(RestSchemaValidator).Namespace;

    public static void ValidateResponse(string resourceFileName,string restResponseContent)
    {
        var resourceFullName = "{0}.{1}".FormatUsing(ResourceLocation,resourceFileName);
        JsonSchema schema;

        // the json file name that is given to this method is stored as a 
        // resource file inside the test project (BuildAction = Embedded Resource)
        using(var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(resourceFullName))
        using(var reader = new StreamReader(stream))
        using (Assembly.GetExecutingAssembly().GetManifestResourceStream(resourceFileName))
        {
            var schematext = reader.ReadToEnd();
            schema = JsonSchema.Parse(schematext);
        }

        var parsedResponse = JObject.Parse(restResponseContent);
        Assert.DoesNotthrow(() => parsedResponse.Validate(schema));
    }
}

这是一个json架构文件的例子.请注意,这是特定于这一个用例,而不是响应DTO类的一般描述.属性都被标记为必需,因为这些是客户端在此用例中期望的特定的属性.模式可能会丢弃响应DTO中当前存在的其他未使用的属性.基于此模式,如果响应JSON中缺少任何预期字段,具有意外数据类型等,则对RestSchemaValidator.ValidateResponse的调用将失败.

{
  "description": "Description of the use case","type": "object","additionalProperties": false,"properties":
  {
    "SomeIntegerField": {"type": "integer","required": true},"SomeArrayField": {
      "type": "array","required": true,"items": {
        "type": "object","properties": {
          "Property1": {"type": "integer","Property2": {"type": "string","required": true}
        }
      }
    }
  }
}

这种类型的测试应该写一次,除非被建模的用例变得过时,否则永远不会被修改.这个想法是这些测试将代表您在生产中的API的实际使用,并确保您的API承诺返回的确切消息不会以破坏现有用途的方式更改.

其他信息

ServiceStack本身有一些针对进程中主机进行测试的examples,上述实现基于此.

c# – 如何编写针对ServiceStack API的功能测试的更多相关文章

  1. 关于h5中的fetch方法解读(小结)

    这篇文章主要介绍了关于h5中的fetch方法解读(小结),fetch身为H5中的一个新对象,他的诞生,是为了取代ajax的存在而出现,有兴趣的可以了解一下

  2. ios – 使用NSURLSession获取JSON数据

    我试图从谷歌距离api使用NSURLSession获取数据,但如下所示,当我打印响应和数据时,我得到的结果为NULL.可能是什么问题?

  3. ios – Facebook应用程序邀请对话框无效

    任何该功能的工作示例都将非常感激.解决方法您的代码很好,当您发布应用程序时,此功能将实际工作,因为某些Facebook集成需要在使用前获得批准.在状态和审核部分提交您的应用以获得批准

  4. ios – 错误域= com.alamofire.error.serialization.response代码= -1011“请求失败:禁止

    任何人都可以帮我解决以下错误–>在AFNetworking2.5中使用“删除”方法时出错解决方法我发现,如果我的手机时钟不同步……它不允许我更新…也许检查你的手机设置到正确的时间“自动区”,看看是否有效…

  5. iOS网页/原生应用Facebook登录弹出 – 失败?

    如果我重新启动app/web-app,用户将自动登录,并重定向到成功页面.我认为是导致问题的原因当您在Firefox/Chrome/Safari浏览器中运行网页时,Facebook登录对话框会弹出一个弹出窗口或另一个选项卡.我相信这是这个弹出页面的一个问题,以及当成功登录时Javascript如何与自身通信.window.close的东西没有返回的根页面…失败的解决方法由于应用程序挂在前面提到的URL上,我决定在shouldStartLoadWithRequest(…)中添加if语句以强制UIWebvie

  6. ios – Watchkit新会话不起作用

    我的手表扩展中有两个视图控制器.每当我打电话时我只得到第一个视图控制器的响应,并在第二个viewcontroller中得到错误WCSession在app和watch扩展中启动.任何建议?

  7. 使用Firebase iOS Swift将特定设备的通知推送到特定设备

    我非常感谢PushNotifications的帮助.我的应用聊天,用户可以直接向对方发送短信.但是如果没有PushNotifications,它就没有多大意义.它全部设置在Firebase上.如何将推送通知从特定设备发送到特定设备?

  8. ios – 保存从查询中获取的用户的属性(即不在currentUser上)

    我有兴趣根据currentUser执行的操作将属性保存到数据库中的用户.基于以下代码,我收到错误消息“除非已通过logIn或signUp验证用户,否则无法保存用户”我想知道是否有一个解决方法,我可以将属性保存到foundUser,而无需登录该用户.谢谢你的帮助!解决方法如果要更新当前不是登录用户的用户,则需要使用主密钥调用Parse.您可以从CloudCode执行此操作;并从您的iOS项目中调用它;

  9. 在iOS中使用NSJSONSerialization进行JSON解析

    解决方法首先在您的JSON响应字典中,在“RESPONSE”键下,您有一个数组而不是字典,该数组包含字典对象.所以要提取用户名和电子邮件ID,如下所示

  10. Xcode:Alamofire获得String响应

    我是IOS开发的新手,目前正在与Alamofire学习网络我正在尝试登录…每当凭证正确时,.PHP文件返回一个json,我可以通过以下代码从Alamofire获取json:现在……当凭证错误时,.PHP不会给json..而且它返回一个字符串..例如“wrong_password”或“userLocked”等等……如何通过Alamofire获得String响应?解决方法如果您希望JSON响应使用.responseJSON,如果您想要String响应,请使用.responseString.如果你想两者同时使用

随机推荐

  1. c# – (wpf)Application.Current.Resources vs FindResource

    所以,我正在使用C#中的WPF创建一个GUI.它看起来像这样:它现在还没有完成.这两行是我尝试制作一种数据表,它们在XAML中是硬编码的.现在,我正在C#中实现添加新的水果按钮功能.我在XAML中有以下样式来控制行的背景图像应该是什么样子:因此,在代码中,我为每列col0,col1和col2创建一个图像,如果我使用以下代码,它添加了一个如下所示的新行:如你所见,它不太正确……为什么一个似乎忽略了一些属性而另一个没有?

  2. c# – 绑定DataGridTemplateColumn

    似乎我已经打了个墙,试图在DataGrid上使用DataTemplates.我想要做的是使用一个模板来显示每个单元格的两行文本.但是似乎无法以任何方式绑定列.以下代码希望显示我想做的事情.注意每个列的绑定:模板列没有这样的东西,因此,这个xaml不可能工作.我注定要将整个DataTemplate复制到每个列,只是对每个副本都有不同的约束?解决方法我不完全确定你想要做什么,但如果您需要获取整行的DataContext,可以使用RelativeSource绑定来移动视觉树.像这样:

  3. c# – 学习设计模式的资源

    最近我来到了这个设计模式的概念,并对此感到非常热情.你能建议一些帮助我深入设计模式的资源吗?

  4. c# – 是否有支持嵌入HTML页面的跨操作系统GUI框架?

    我想开发一个桌面应用程序来使用跨系统,是否有一个GUI框架,允许我为所有3个平台编写一次代码,并具有完全可脚本化的嵌入式Web组件?我需要它有一个API来在应用程序和网页之间进行交流.我知道C#,JavaScript和一些python.解决方法Qt有这样的事情QWebView.

  5. c# – 通过字符串在对象图中查找属性

    我试图使用任意字符串访问嵌套类结构的各个部分.给出以下(设计的)类:我想要从Person对象的一个实例的“PersonsAddress.HousePhone.Number”获取对象.目前我正在使用反思来做一些简单的递归查找,但是我希望有一些忍者有更好的想法.作为参考,这里是我开发的(crappy)方法:解决方法您可以简单地使用标准的.NETDataBinder.EvalMethod,像这样:

  6. c# – 文件下载后更新页面

    FamilyID=0a391abd-25c1-4fc0-919f-b21f31ab88b7&displaylang=en&pf=true它呈现该页面,然后使用以下元刷新标签来实际向用户提供要下载的文件:你可能需要在你的应用程序中做类似的事情.但是,如果您真的有兴趣在文件完全下载后执行某些操作,那么您的运气不佳,因为没有任何事件可以与浏览器进行通信.执行此操作的唯一方法是上传附件时使用的AJAXupload.

  7. c# – 如何在每个机器应用程序中实现单个实例?

    我必须限制我的.net4WPF应用程序,以便每台机器只能运行一次.请注意,我说每个机器,而不是每个会话.我使用一个简单的互斥体实现单实例应用程序,直到现在,但不幸的是,这样一个互斥是每个会话.有没有办法创建机器互连,还是有其他解决方案来实现每个机器应用程序的单个实例?

  8. c# – WCF和多个主机头

    我的雇主网站有多个主机名,都是同一个服务器,我们只是显示不同的皮肤来进行品牌宣传.不幸的是,在这种情况下,WCF似乎不能很好地工作.我试过overridingthedefaulthostwithacustomhostfactory.这不是一个可以接受的解决方案,因为它需要从所有主机工作,而不仅仅是1.我也看过thisblogpost,但是我无法让它工作,或者不是为了解决我的问题.我得到的错误是“这

  9. c# – ASP.NET MVC模型绑定与表单元素名称中的虚线

    我一直在搜索互联网,试图找到一种方式来容纳我的表单元素的破折号到ASP.NET的控制器在MVC2,3或甚至4中的默认模型绑定行为.作为一名前端开发人员,我更喜欢在我的CSS中使用camelCase或下划线进行破折号.在我的标记中,我想要做的是这样的:在控制器中,我会传入一个C#对象,看起来像这样:有没有办法通过一些正则表达式或其他行为来扩展Controller类来适应这种情况?我讨厌这样的事实,我必须这样做:甚至这个:思考?

  10. c# – 用户界面设计工具

    我正在寻找一个用户界面设计工具来显示文档中可能的GUI.我不能生成代码.我知道MicrosoftVisio提供了一个功能.但有什么办法吗?您使用哪种软件可视化GUI?

返回
顶部