请原谅我的无知,因为我是一个相当新的程序员,更不用说测试员了,我的问题涉及我组织中 API 的测试。我的任务是执行集成测试,确保我们在 Web 应用程序中使用的 API 调用返回正确的 HTTP 状态代码。在某些情况下,我还确认 API 实际上已将数据输入数据库。我正在使用 MSTest 通过 Visual Studio Integrated Test Runner 运行我的测试。目标是有一套测试,我们可以在每晚的基础上运行我们的 drop。
继续....
我正在使用的示例处理多个 PatronMessage api 调用。
该系统能够
- 返回系统定义的消息列表
- 返回读者特定消息的列表
- 向读者添加新消息
- 修改读者消息
- 删除读者消息
我编写代码的方式是,我有一个 xml 文件,其中包含属性 apiObject(PatronMessages) 和 objectAction(GetSystemDefined-Successful) 以及其他属性,但这些用于测试识别目的。
我还有一个名为 TestCase 的抽象类,它有几个实现的、虚拟的和抽象的方法。然后我创建了一个 PatronMessagesTestCase 类,它扩展了 TestCase 并实现了所有抽象并覆盖了 TestCase 中的一些虚拟方法。然后将来自 TestCase 的 RunTest 具体方法传递给 apiObject 和 objectAction 并从 xml 文件返回测试参数以运行测试。此 RunTest 方法返回一个布尔值,指示测试是否通过。
我遇到的问题是大多数测试的验证方式完全不同。我有一个 MakeDatabaseCall 方法,它在 PatronMessagesTestCase 中实现了 TestCase 的抽象方法,但是这个 MakeDatabaseCall 可能不同,也可能不同。例如:
为了测试添加新消息,我将检查数据库值以获得最高消息 ID,然后执行 api 调用,然后检查数据库值并确认调用后的消息 ID 更大。虽然对于删除消息测试,我可能会运行相同的测试,但检查数据库值是否低于先前检查的值。
我知道我可以添加一个开关并打开 objectAction,但是我的 PatronMessagesTestCase 中的每个方法都需要打开 objectAction,这对我来说似乎是错误的。
再一次继续前进…………
所以昨天我向一位高级开发人员寻求帮助,他建议我使用一个接口(ITestCase)来构造一个测试用例,然后有一个实现 ITestCase 的类(PatronAssociationsTestCase)并创建一个运行所有测试用例的测试运行器类但我做了一点,我有点困惑这将如何帮助我的情况(我真的认为它只会让情况变得更糟。但这是我的代码,请记住,我几乎试图两次完成同样的事情, 只是在两个不同的地方以两种不同的方式。另外请记住,这不是完整的代码,所以它可能会引发一些错误(我开始工作并且有点卡在我的想法上)......
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace LEAPIntegrationTestingFINAL
{
public interface ITestCase
{
public Dictionary<string, object> GetTestParams(string apiObject, string objectAction);
public string ConfigureURL(string objectAction);
//public Dictionary<string, object> ConfigureRequestBody(Dictionary<string, object> testParams); //<=-=-=-Some tests need to
public Dictionary<string, object> ExecuteRequest(Dictionary<string, object> testParams);
public Dictionary<string, object> ParseResponse(Dictionary<string, object> responseValues);
//public Dictionary<string, object> MakeDatabaseCall(Dictionary<string, object> testParams); //<=-=-=-Some tests need to
public bool CompareData(Dictionary<string, object> databaseValues, Dictionary<string, object> responseValues);
}
public static class TestRunner
{
public static bool RunTest(ITestCase TestCase, string apiObject, string objectAction)
{
bool comparisonValue = false;
return comparisonValue;
}
}
public class PatronAssociationsTestCase : ITestCase
{
public Dictionary<string, object> GetTestParams(string apiObject, string objectAction)
{
throw new NotImplementedException();
}
public string ConfigureURL(string objectAction)
{
throw new NotImplementedException();
}
public Dictionary<string, object> ConfigureRequestBody(Dictionary<string, object> testParams)
{
throw new NotImplementedException();
}
public Dictionary<string, object> ExecuteRequest(Dictionary<string, object> testParams)
{
throw new NotImplementedException();
}
public Dictionary<string, object> ParseResponse(Dictionary<string, object> responseValues)
{
throw new NotImplementedException();
}
public Dictionary<string, object> MakeDatabaseCall(Dictionary<string, object> testParams)
{
throw new NotImplementedException();
}
public bool CompareData(Dictionary<string, object> databaseValues, Dictionary<string, object> responseValues)
{
throw new NotImplementedException();
}
}
}
<?xml version="1.0" encoding="utf-8" ?>
<TestParameterFile>
<Messages action="GETSystemDefined-Succesful"
method="GET" expectedReponseCode ="200"/>
<Messages action="AddPatronMessage-InvalidPatronID"
method = "POST" expResponseCode="404" requestBody="{"MessageType": 101,"MessageValue": "This is a free text message for this patron!"}"/>
</TestParameterFile>
这是我尝试实现的原始方式(我认为更好的方式),并且如果可能的话希望合并一个接口
using System;
using System.Collections.Generic;
using System.Configuration;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading.Tasks;
using System.Web.Script.Serialization;
using System.Data.SqlClient;
namespace LEAPIntegrationTestingFINAL
{
public abstract class TestCase
{
public static string authorizationString
{
get
{
string statusCode;
AppConfig.Change("D:\\SampleProjects\\LEAP\\LEAPIntegrationTestingFINAL\\LEAPIntegrationTestingFINAL\\TestingSuiteConfiguration.config");
//Converting User credentials to base 64.
string uri = ConfigurationManager.AppSettings["authURL"];
string uname = ConfigurationManager.AppSettings["username"];
string pword = ConfigurationManager.AppSettings["password"];
string _auth = string.Format("{0}:{1}", uname, pword);
string _enc = Convert.ToBase64String(Encoding.ASCII.GetBytes(_auth));
string _cred = string.Format("{0} {1}", "Basic", _enc);
WebRequest authRequest = WebRequest.Create(uri);
authRequest.Headers.Add(HttpRequestHeader.Authorization, _cred);
authRequest.ContentLength = 0;
authRequest.Method = "POST";
//Creating and receiving WebResponse
HttpWebResponse authResponse = (HttpWebResponse)authRequest.GetResponse();
statusCode = Convert.ToString((int)authResponse.StatusCode);
using (Stream responseStream = authResponse.GetResponseStream())
{
using (StreamReader sr = new StreamReader(responseStream))
{
Dictionary<string, object> authResponseObject = new Dictionary<string, object>();
string authJSON = sr.ReadLine();
sr.Close();
responseStream.Close();
//Deserialization of jSON object and creating authorization String
JavaScriptSerializer jsSerializer = new JavaScriptSerializer();
authResponseObject = jsSerializer.Deserialize<Dictionary<string, object>>(authJSON);
string AccessToken = authResponseObject["AccessToken"].ToString();
string AccessSecret = authResponseObject["AccessSecret"].ToString();
string authorizationString = "Authorization: PAS " + AccessToken + ":" + AccessSecret;
return authorizationString;
}
}
}
}
public string url { get; set; }
public Dictionary<string, object> testParams { get; set; }
public Dictionary<string, object> responseValues { get; set; }
public Dictionary<string, object> databaseValues { get; set; }
public bool comparisonValue { get; set; }
public bool RunTest(string apiObject, string objectAction)
{
bool comparisonValue = false;
this.testParams = GetTestParams(apiObject, objectAction);
this.url = RootURLBuilder.Build();
this.url = ConfigureURL(this.url);
#region ConfigRequestBody
if (this.testParams.ContainsKey("requestBody"))
{
this.testParams = ConfigureRequestBody(this.testParams);
}
#endregion
this.responseValues = ExecuteRequest(this.testParams);
this.responseValues = ParseResponse(this.responseValues);
#region MakeDatabaseCall
if (this.testParams.ContainsKey("query"))
{
this.databaseValues = MakeDatabaseCall(this.testParams);
}
#endregion
this.comparisonValue = CompareData(this.databaseValues, this.responseValues);
return comparisonValue;
}
private Dictionary<string, object> GetTestParams(string apiObject, string objectAction)
{
Dictionary<string, object> testParams = new Dictionary<string, object>();
return testParams;
}
public abstract string ConfigureURL(string objectAction);
public abstract Dictionary<string, object> ConfigureRequestBody(Dictionary<string,object> testParams);
public Dictionary<string, object> ExecuteRequest(Dictionary<string, object> testParams)
{
Dictionary<string, object> responseValues = new Dictionary<string, object>();
string requestBody = null;
string httpVerb = this.testParams["method"].ToString();
#region requestBody = this.testParams["requestBody"].ToString();
if (this.testParams.ContainsKey("requestBody"))
{
requestBody = this.testParams["requestBody"].ToString();
}
#endregion
WebRequest req = WebRequest.Create(this.url);
req.Headers.Add(authorizationString);
req.ContentType = "application/json";
req.Method = httpVerb;
if (requestBody != null)
{
req.ContentLength = requestBody.Length;
using (Stream reqStream = req.GetRequestStream())
{
byte[] reqBodyBytes = Encoding.UTF8.GetBytes(requestBody);
reqStream.Write(reqBodyBytes, 0, reqBodyBytes.Length);
}
}
else
req.ContentLength = 0;
using (HttpWebResponse resp = (HttpWebResponse)req.GetResponse())
{
string responseJSON;
int statusCode = (int)resp.StatusCode;
string statusMessage = resp.StatusDescription;
Stream respStream = resp.GetResponseStream();
StreamReader sr = new StreamReader(respStream);
responseJSON = sr.ReadToEnd();
respStream.Close();
sr.Close();
responseValues.Add("statusCode", statusCode);
responseValues.Add("statusMessage", statusMessage);
responseValues.Add("responseJSON", responseJSON);
}
return responseValues;
}
public virtual Dictionary<string, object> ParseResponse(Dictionary<string, object> responseValues)
{
Dictionary<string, object> parsedResponseJSON = new Dictionary<string, object>();
Dictionary<string, object> parsedResponse = new Dictionary<string, object>();
string json = this.responseValues["responseJSON"].ToString();
parsedResponse.Add("statusCode", this.responseValues["statusCode"]);
parsedResponse.Add("statusMessage", this.responseValues["statusMessage"]);
return parsedResponse;
}
public abstract Dictionary<string, object> MakeDatabaseCall(Dictionary<string, object> testParams);
public abstract bool CompareData(Dictionary<string, object> databaseValues, Dictionary<string, object> responseValues);
}
public class PatronMessageTestCase : TestCase
{
public override string ConfigureURL(string objectAction)
{
string url;
switch (objectAction)
{
case "GETSystemDefined-Succesful":
url = this.url + "/patronmessages";
return url;
default:
return "Not Found";
}
}
public override Dictionary<string, object> ConfigureRequestBody(Dictionary<string, object> testParams)
{
throw new NotImplementedException();
}
public override Dictionary<string, object> MakeDatabaseCall(Dictionary<string, object> testParams)
{
throw new NotImplementedException();
}
public override bool CompareData(Dictionary<string, object> databaseValues, Dictionary<string, object> responseValues)
{
throw new NotImplementedException();
}
}
}
最后.....运行测试并使用 MSTest 控制的文件
using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace LEAPIntegrationTestingFINAL
{
[TestClass]
public class LEAPTestingSuite
{
[TestMethod]
public void MessagesTests()
{
PatronMessageTestCase MessagesTests = new PatronMessageTestCase();
Assert.AreEqual(true, MessagesTests.RunTest("Messages", "GETSystemDefined-Succesful"));
}
[TestMethod]
public void AssociationsTests()
{
PatronAssociationsTestCase GETSystemDefined = new PatronAssociationsTestCase();
Assert.AreEqual(true, TestRunner.RunTest());
}
}
}
如果您需要其中的任何一个,我们还有更多的类让我知道......并且只是回顾一下,我的主要目标是进行 api 调用并确保返回的响应代码与 xml 文件中的 expectedResponseCode 匹配。我还可以选择进行数据库调用并确认某些数据库值与从 api 调用返回的某些数据相匹配。另外,我的同事建议使用界面