Nunit批量运行CSV上传测试超时,单测及本地正常求排查
嘿,看起来你碰到了个挺头疼的测试稳定性问题——批量跑就超时,单独跑、本地测都正常,浏览器直接访问也没毛病,咱们一步步拆解看看问题出在哪:
先梳理下核心问题现象
- 测试栈:NUnit + SpecFlow + RestSharp,调用Web控制器上传CSV
- 异常场景:批量运行测试/非本地环境会触发超时错误;单独跑测试、本地localhost运行应用时完全正常
- 已尝试方案:移除静态属性存储值、添加线程延迟,均无效
结合你的代码,分析几个大概率的问题点
1. 测试上下文的状态污染(最可能的元凶)
看你代码里用了User.cookieContainer和User.target这类全局静态属性存储会话状态——批量测试时,多个测试线程很可能共享了同一个Cookie容器或会话数据,导致请求互相干扰:比如前一个测试的过期Cookie被后一个测试复用,或者会话状态混乱引发服务器端的处理阻塞,最终超时。
2. RestClient实例滥用与连接池耗尽
你的每个方法都在新建RestClient实例,但RestClient内部自带连接池管理,频繁创建新实例会导致目标环境的HTTP连接被快速耗尽,尤其是批量测试并发请求时,服务器端无法及时处理过多的新建连接,自然就超时了。
3. 硬编码的UTC时间戳
Upload方法里的utc=1580117555355是固定死的旧时间戳,目标环境可能会校验这个参数的时效性——批量测试时这个时间早就过期,服务器端可能在等待合法参数或者做额外校验,拖慢了响应速度。
4. Cookie手动添加的潜在问题
你同时用SignInResponse.Cookies和User.cookieContainer添加Cookie,很可能出现Cookie重复、Domain/Path不匹配的情况:非本地环境的域名和本地不同,手动添加的Cookie可能无法被服务器识别,导致请求需要重新登录,但登录流程又因会话混乱卡住,最终超时。
针对性的解决方案
1. 用SpecFlow上下文注入实现测试完全隔离
创建一个专属的测试上下文类,为每个测试场景分配独立的实例,彻底避免状态共享:
// 定义测试上下文,存储每个场景的独立状态 public class TestSessionContext { public RestClient RestClient { get; set; } public CookieContainer CookieContainer { get; set; } public string RequestVerificationToken { get; set; } } // 在步骤定义里注入上下文 [Binding] public class UploadSteps { private readonly TestSessionContext _sessionContext; private readonly IConfiguration _config; public UploadSteps(TestSessionContext sessionContext, IConfiguration config) { _sessionContext = sessionContext; _config = config; // 初始化每个场景的RestClient和Cookie容器 _sessionContext.RestClient = new RestClient(_config["Web_URL"]) { Timeout = 30000, // 设置合理的超时时间,比如30秒 CookieContainer = new CookieContainer() }; } // 改写获取RVToken的方法,用上下文存储 [Given("我获取了请求验证令牌")] public void GetRequestVerificationToken() { var request = new RestRequest("Account/SignIn", Method.GET); var response = _sessionContext.RestClient.Execute(request); var html = new HtmlDocument(); html.LoadHtml(response.Content); _sessionContext.RequestVerificationToken = html.DocumentNode .SelectSingleNode("//form/input[@name='__RequestVerificationToken']") .Attributes["value"].Value; } }
2. 复用RestClient并优化连接配置
RestClient是线程安全的,建议在整个测试场景中复用同一个实例,避免频繁创建连接。同时可以调整连接池参数:
// 在RestClient初始化时配置连接池 _sessionContext.RestClient = new RestClient(_config["Web_URL"]) { Timeout = 30000, CookieContainer = new CookieContainer(), // 调整连接池大小,适配目标环境的并发限制 ConfigureWebRequest = req => { req.ServicePoint.ConnectionLimit = 10; req.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate; } };
3. 动态生成UTC时间戳
把硬编码的时间戳改成实时生成的当前时间:
[When("我上传类型为{string}的CSV文件")] public IRestResponseUploadFlex(string flexType, string filePath) { var utcTimestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); var uploadRequest = new RestRequest($"audiences/{flexType}/upload?utc={utcTimestamp}", Method.POST); uploadRequest.AddFile("targetAreaFile", filePath); uploadRequest.AddParameter("name", "targetAreaFile"); // 不需要手动加Cookie,RestClient的CookieContainer会自动携带会话 return _sessionContext.RestClient.Execute(uploadRequest); }
4. 简化Cookie管理,依赖RestClient自动处理
放弃手动添加Cookie的方式,让RestClient的CookieContainer自动管理会话:
[When("我用账号{string}密码{string}登录")] public void SignIn(string email, string password) { var signInRequest = new RestRequest("Account/SignIn", Method.POST); signInRequest.AddParameter("Email", email); signInRequest.AddParameter("Password", password); signInRequest.AddParameter("__RequestVerificationToken", _sessionContext.RequestVerificationToken); signInRequest.AddParameter("RememberMe", false); signInRequest.AddHeader("Content-Type", "application/x-www-form-urlencoded"); // 登录后,Cookie会自动存入RestClient的CookieContainer _sessionContext.RestClient.Execute(signInRequest); }
5. 加日志排查超时细节
在关键请求节点添加日志,记录请求耗时、状态码、参数,方便批量测试时定位具体哪一步超时:
var stopwatch = Stopwatch.StartNew(); var response = _sessionContext.RestClient.Execute(uploadRequest); stopwatch.Stop(); // 用你项目里的日志组件输出 _logger.Debug("上传请求耗时:{Elapsed}ms,状态码:{StatusCode}", stopwatch.ElapsedMilliseconds, response.StatusCode); _logger.Debug("响应内容:{Content}", response.Content);
最后补充排查方向
如果以上方案还没解决,建议检查:
- 目标环境的服务器是否限制了同一IP的并发请求数
- 目标环境的网络延迟是否更高,需要调大RestClient的超时时间
- 批量测试的并行度是否过高,在NUnit里用
[Parallelizable(ParallelScope.Self)]限制并发数
内容的提问来源于stack exchange,提问作者Prany

