使用C#.NET和Nunit(.NET)对SSRS进行自动化动态报表协调流程





0/5 (0投票)
此流程可以设置为每天运行一次,以确保相互关联的报表能够相互协调,从而确保报表质量的一致性。
引言
作为数据仓库开发人员,我们都希望我们的每日报表是正确且最新的。但是,大多数时候,即使报表经过几个阶段的测试,也可能在生产环境中出现新的错误。这可能是由于ETL流程中的问题引起的。大多数时候,报表应该相互协调。想象一下,一个报表包含一个值,而另一个报表包含不同的值。这可能会导致用户对整个数据仓库失去信心。因此,重要的是要有一种动态的方法来协调相关的报表。下面提到的方法是我想出的解决这个问题的方法,它使用C#和SSRS生成的报表(使用其XML格式)。
背景
此流程的目的是帮助数据仓库开发人员和管理员通过使用自动化流程来协调相关的报表,从而维护其报表服务。这最终可以帮助提高数据仓库或报表服务器中报表质量。一旦需要协调的属性设置好,测试用例将为每个数据行动态生成。
使用代码
步骤 1
首先在SSRS报表服务器中创建一个订阅,以生成报表并将文件作为**XML格式**发送到服务器上的某个位置或任何可访问的文件夹。
确保需要协调的报表都以这种方式订阅,这样当C#代码启动时,它们就可以使用了。
第二步
在数据库端创建所需的表,这些表将用于设置测试用例。这些表的SQL查询如下所示。
//Create UnitTest table where all unit test case details are stored
CREATE TABLE [dbo].[UnitTest](
[UnitTestID] [int] IDENTITY(1,1) NOT NULL,
[UnitTestName] [nvarchar](250) NULL,
[ParameterOne] [nvarchar](100) NULL,
[ParameterTwo] [nvarchar](100) NULL
) ON [PRIMARY]
//Create sub table which hold information on which properties in the report should be tested against each other
CREATE TABLE [dbo].[UnitTestReportDetail](
[UnitTestReportDetailID] [int] IDENTITY(1,1) NOT NULL,
[UnitTestID] [int] NULL,
[ReportPropertyName] [nvarchar](100) NULL,
[ParameterOneElementCollectionName] [nvarchar](100) NULL,
[ParameterOneDescendantName] [nvarchar](100) NULL,
[ParameterOneAttributeName] [nvarchar](100) NULL,
[ParameterTwoElementCollectionName] [nvarchar](100) NULL,
[ParameterTwoDescendantName] [nvarchar](100) NULL,
[ParameterTwoAttributeName] [nvarchar](100) NULL,
[Variance] [int] NULL,
CONSTRAINT [PK_UnitTestReportDetail] PRIMARY KEY CLUSTERED
(
[UnitTestReportDetailID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
步骤 3
创建一个C#控制台应用程序。
免费下载.NET的Nunit框架,并将其包含在创建的控制台项目中。
步骤 4
首先创建实体类,该类将保存两个报表中需要比较的每个属性的值。
public class ReportValueEntity
{
public string ReportReconciliatioName { get; set; }
public string ReportPropertyName { get; set; }
public string ReportCollectionValue { get; set; }
public decimal ReportOneValue { get; set; }
public decimal ReportTwoValue { get; set; }
public decimal VarianceValue { get; set; }
public override string ToString()
{
return string.Format("{0} - {1}({2}): Expected - {3} to {4} but Actual {5}", ReportReconciliatioName, ReportPropertyName, ReportCollectionValue, (ReportOneValue - VarianceValue).ToString(),(ReportOneValue + VarianceValue), ReportTwoValue);
}
}
步骤 5
创建**DataSource.cs**类,该类将保存从XML报表格式检索报表值的方法。
public class DataSource
{
public static List AllReportValues()
{
return GetAllReportValues();
}
public static List GetAllReportValues()
{
try
{
DAO dao = new DAO();
DataTable tableValueTest_DT = dao.getDataTable("SELECT * FROM UnitTest WHERE TestTypeID = " + ((int)TestTypeEnum.Report_Values_Test).ToString());
List allValues = new List();
foreach (DataRow unitTest in tableValueTest_DT.Rows)
{
string reportOne_Name = unitTest["ParameterOne"].ToString();
string reportTwo_Name = unitTest["ParameterTwo"].ToString();
int reportID = int.Parse(unitTest["UnitTestID"].ToString());
XDocument xmlFileOne = XDocument.Load(string.Format(@"report one path.xml", reportOne_Name));
XDocument xmlFileTwo = XDocument.Load(string.Format(@"report two path.xml", reportTwo_Name));
DataTable reportValueTest_DT = dao.getDataTable("SELECT * FROM UnitTestReportDetail WHERE UnitTestID = " + reportID.ToString());
XNamespace dfOne = xmlFileOne.Root.Name.Namespace;
XNamespace dfTwo = xmlFileTwo.Root.Name.Namespace;
foreach (DataRow testProperty in reportValueTest_DT.Rows)
{
List collectionList = xmlFileOne.Descendants(dfOne + testProperty["ParameterOneElementCollectionName"].ToString()).ToList();
List collectionList2 = xmlFileTwo.Descendants(dfTwo + testProperty["ParameterTwoElementCollectionName"].ToString()).ToList();
foreach (XElement item in collectionList)
{
ReportValueEntity reportValueEntity = new ReportValueEntity();
reportValueEntity.ReportReconciliatioName = unitTest["UnitTestName"].ToString();
reportValueEntity.ReportPropertyName = testProperty["ReportPropertyName"].ToString();
reportValueEntity.VarianceValue = decimal.Parse(testProperty["Variance"].ToString());
reportValueEntity.ReportCollectionValue = item.Attributes().FirstOrDefault().Value.ToString();
XElement item2 = collectionList2.Where(a => a.Attributes().FirstOrDefault().Value == reportValueEntity.ReportCollectionValue).FirstOrDefault();
if (item2 != null)
{
XElement element = item.Descendants(dfOne + testProperty["ParameterOneDescendantName"].ToString()).FirstOrDefault();
XElement element2 = item2.Descendants(dfTwo + testProperty["ParameterTwoDescendantName"].ToString()).FirstOrDefault();
if (element != null || element2 != null)
{
if (element.Attributes(testProperty["ParameterOneAttributeName"].ToString()).FirstOrDefault() != null)
reportValueEntity.ReportOneValue = decimal.Parse(element.Attributes(testProperty["ParameterOneAttributeName"].ToString()).FirstOrDefault().Value.ToString());
if (element2.Attributes(testProperty["ParameterTwoAttributeName"].ToString()).FirstOrDefault() != null)
reportValueEntity.ReportTwoValue = decimal.Parse(element2.Attributes(testProperty["ParameterTwoAttributeName"].ToString()).FirstOrDefault().Value.ToString());
allValues.Add(reportValueEntity);
}
}
}
}
}
return allValues;
}
catch (Exception ex)
{
throw ex;
}
}
}
DAO.cs和Connection.cs类将保存从我们之前创建的UnitTest表检索数据的简单基本方法。
public class DAO
{
public DataTable getDataTable(string query)
{
try
{
DataTable dt = new DataTable();
DataSet ds = new DataSet();
Connection connection = new Connection();
ds = connection.GetDataSet(query);
dt = ds.Tables[0];
return dt;
}
catch (Exception ex)
{
throw ex;
}
}
}
public class Connection
{
SqlConnection sqlConnection;
public Connection()
{
if (sqlConnection == null || sqlConnection.State == ConnectionState.Closed)
{
sqlConnection = getConnection();
}
}
public SqlConnection getConnection()
{
return sqlConnection = new SqlConnection(ConfigurationManager.ConnectionStrings["Dev"].ConnectionString);
}
public void openConnection()
{
if (sqlConnection.State != ConnectionState.Open)
{
sqlConnection.Open();
}
}
public void closeConnection()
{
if (sqlConnection.State != ConnectionState.Closed)
{
sqlConnection.Close();
}
}
public DataSet GetDataSet(string query)
{
SqlDataAdapter dataAdapater = new SqlDataAdapter();
DataSet dataSet = new DataSet();
SqlCommand command = sqlConnection.CreateCommand();
command.CommandText = query;
dataAdapater.SelectCommand = command;
dataAdapater.Fill(dataSet);
return dataSet;
}
}
在App.config文件中配置连接字符串。
下面的枚举可用于扩展此流程,以包括在数据集之间协调数据集和行计数。
public enum TestTypeEnum
{
Table_Values_Test = 1,
Report_Values_Test = 2,
Row_Count_Test = 3
}
步骤 6
现在创建ComparisonTest.cs类,该类将保存所有测试用例。当调用或执行程序集时,此类中的方法将被执行。
[TestFixture]
class ComparisonTest
{
[Test, TestCaseSource(typeof(DataSource), "AllReportValues")]
public void CompareReportValues(ReportValueEntity entityRow)
{
Assert.That(entityRow.ReportTwoValue, Is.InRange((entityRow.ReportOneValue - entityRow.VarianceValue), (entityRow.ReportOneValue + entityRow.VarianceValue)));
}
}
步骤 7
现在指定要协调的属性在表中。示例如下所示,
一旦指定了相关的报表名称和类型,就可以开始详细指定如何在报表中协调哪些属性。
方差列可用于指定一个值范围。例如,使用我们指定的方差值来检查报表二的值是否在特定范围内。[参见ComparisonTest.cs中的代码]
下面我粘贴了报表XML的部分内容,以便理解我已经设置了哪些区域与上面的屏幕截图进行协调。
报表一 -
报表二 -
上面的例子展示了如何在两个单独的报表中折扣值相互协调。
步骤 8
完成上述所有步骤后。构建C#项目,转到C#项目的bin文件夹,双击创建的.dll文件。确保在此步骤之前已安装Nunit框架。然后,您将能够看到上面提到的属性如何针对上面提到的ParameterElementCollection的所有行相互协调。
关注点
此流程可以通过Jenkins等持续集成工具进行自动化。使用Jenkins之类的CI工具,我们可以确保报表在每次提交和每次ETL流程中不会中断,并保持报表的高质量。
在我的下一篇文章中,我们将了解如何配置Jenkins来托管此类测试项目,以便每天或按计划周期运行。