Commit 8645cf14 authored by André-Claude Gendron's avatar André-Claude Gendron

Initial importation of Mocking Framework presented at the InterSystems Summit 2017.

parents
<?xml version="1.0" encoding="UTF-8"?>
<Export generator="Cache" version="25">
<Class name="Fw.CResultSetTools">
<Abstract>1</Abstract>
<TimeCreated>62650,40207.355394</TimeCreated>
<Method name="GetNewResultSet">
<Description>
Used to retrieve a resultSet obtained by "%New". Testcases can override
the %ResultSet to return a CMockResultSet instead.
strQueryName is composed of "ClassName:QueryName" and can be omitted or empty.</Description>
<ClassMethod>1</ClassMethod>
<FormalSpec>strQueryName:%String=""</FormalSpec>
<ReturnType>%ResultSet</ReturnType>
<Implementation><![CDATA[
#Dim rs As %ResultSet = $$$NULLOREF
try
{
set rs = $ClassMethod("Tests.Fw.Mock.CMockManager", "GetMockResultSet", strQueryName)
}
catch
{
// Trying to access an undefined classmethod throws an error. Ignore it.
}
// If there are no defined CMockResultSet, create a standard one.
set:('$IsObject(rs)) rs = ##class(%ResultSet).%New(strQueryName)
quit rs
]]></Implementation>
</Method>
</Class>
</Export>
<?xml version="1.0" encoding="UTF-8"?>
<Export generator="Cache" version="25">
<Class name="MockDemo.CTestedClass">
<Import>Fw</Import>
<Super>%RegisteredObject</Super>
<TimeCreated>64377,68751.494372</TimeCreated>
<Property name="mathService">
<Type>MathService</Type>
<Private>1</Private>
</Property>
<Method name="%OnNew">
<FormalSpec>mathService:MathService=##class(MathService).%New()</FormalSpec>
<Private>1</Private>
<ReturnType>%Status</ReturnType>
<ServerOnly>1</ServerOnly>
<Implementation><![CDATA[
set ..mathService = mathService
quit $$$OK
]]></Implementation>
</Method>
<Method name="SumOperation">
<FormalSpec>A:%Integer,B:%Integer</FormalSpec>
<ReturnType>%Integer</ReturnType>
<Implementation><![CDATA[ quit ..mathService.Add(A, B)
]]></Implementation>
</Method>
<Method name="ComplexOperation">
<FormalSpec><![CDATA[A:%Integer,B:%Integer,&status:%Status=$$$OK]]></FormalSpec>
<ReturnType>%Integer</ReturnType>
<Implementation><![CDATA[
if (..mathService.GreaterThan(A, B))
{
quit ..mathService.Divide(A, B, .status)
}
else
{
quit ..mathService.Multiply(A, B, .status)
}
]]></Implementation>
</Method>
<Method name="FetchSQLQueryInformation">
<FormalSpec>input:%String</FormalSpec>
<ReturnType>%String</ReturnType>
<Implementation><![CDATA[
#Dim returnedValue As %String = ""
#Dim rs As %ResultSet = ##class(CResultSetTools).GetNewResultSet($ClassName() _ ":MyQuery")
do rs.Execute(input)
if (rs.Next())
{
set returnedValue = rs.ResultA _ "+" _ rs.ResultB
}
quit returnedValue
]]></Implementation>
</Method>
<Query name="MyQuery">
<Type>%SQLQuery</Type>
<FormalSpec>input:%String</FormalSpec>
<SqlQuery> select 'A' As ResultA, :input As ResultB</SqlQuery>
</Query>
<Method name="FetchSQLPrepareInformation">
<FormalSpec>input:%String</FormalSpec>
<ReturnType>%String</ReturnType>
<Implementation><![CDATA[
#Dim returnedValue As %String = ""
#Dim rs As %ResultSet = ##class(CResultSetTools).GetNewResultSet("")
do rs.Prepare("select 'B' As ResultA, ? As ResultB")
do rs.Execute(input)
while (rs.Next())
{
set returnedValue = (returnedValue _ "|" _ rs.ResultA _ "+" _ rs.ResultB)
}
quit returnedValue
]]></Implementation>
</Method>
<Method name="PopulateObjectProperties">
<FormalSpec><![CDATA[&hl7Message:EnsLib.HL7.Message,searchTable:EnsLib.XML.SearchTable]]></FormalSpec>
<ReturnType>%Status</ReturnType>
<Implementation><![CDATA[
// Build a stream
set expectedStream = ##class(%Stream.TmpCharacter).%New()
do expectedStream.Write("This is some content")
quit ..mathService.GetResult(.hl7Message, searchTable, expectedStream)
]]></Implementation>
</Method>
</Class>
</Export>
<?xml version="1.0" encoding="UTF-8"?>
<Export generator="Cache" version="25">
<Class name="MockDemo.CTestedClassWithoutInjection">
<Super>%RegisteredObject</Super>
<TimeCreated>64377,68895.376089</TimeCreated>
<Method name="SumOperation">
<FormalSpec>A:%Integer,B:%Integer</FormalSpec>
<ReturnType>%Integer</ReturnType>
<Implementation><![CDATA[
set mathService = ##class(MathService).%New()
quit mathService.Add(A, B)
]]></Implementation>
</Method>
</Class>
</Export>
<?xml version="1.0" encoding="UTF-8"?>
<Export generator="Cache" version="25">
<Class name="MockDemo.MathService">
<Super>%RegisteredObject</Super>
<TimeCreated>64377,68863.384867</TimeCreated>
<Method name="Add">
<FormalSpec>A:%Integer,B:%Integer</FormalSpec>
<ReturnType>%Integer</ReturnType>
<Implementation><![CDATA[ quit A+B
]]></Implementation>
</Method>
<UDLText name="T">
<Content><![CDATA[
/// TODO : Implement division some day
]]></Content>
</UDLText>
</Class>
</Export>
<?xml version="1.0" encoding="UTF-8"?>
<Export generator="Cache" version="25">
<Class name="MockDemo.UnitTests.CTestTestedClass">
<Super>Tests.Fw.CUnitTestBase</Super>
<TimeCreated>64377,70711.892343</TimeCreated>
<Property name="mathService">
<Type>MockDemo.MathService</Type>
<Private>1</Private>
</Property>
<Property name="testedClass">
<Type>MockDemo.CTestedClass</Type>
<Private>1</Private>
</Property>
<UDLText name="T">
<Content><![CDATA[
// --- Run test ---
]]></Content>
</UDLText>
<Method name="RunTests">
<ClassMethod>1</ClassMethod>
<Implementation><![CDATA[ do ##super()
]]></Implementation>
</Method>
<Method name="OnBeforeOneTest">
<FormalSpec>testname:%String</FormalSpec>
<ReturnType>%Status</ReturnType>
<Implementation><![CDATA[
set ..mathService = ..CreateMock()
set ..testedClass = ##class(MockDemo.CTestedClass).%New(..mathService)
quit $$$OK
]]></Implementation>
</Method>
<UDLText name="T">
<Content><![CDATA[
// --- Tests for SumOperation ---
]]></Content>
</UDLText>
<Method name="TestSumOperation">
<Implementation><![CDATA[
do ..Expect(..mathService.Add(1, 2)).AndReturn(3).Times(3)
do ..ReplayAllMocks()
do $$$AssertEquals(..testedClass.SumOperation(1, 2), 3)
do $$$AssertEquals(..testedClass.SumOperation(1, 2), 3)
do $$$AssertEquals(..testedClass.SumOperation(1, 2), 3)
do ..VerifyAllMocks()
]]></Implementation>
</Method>
<Method name="TestSumOperationParameterError">
<Description>
This tests fails only because it is intended to for demo purpose.</Description>
<Implementation><![CDATA[
do ..Expect(..mathService.Add(1, 2)).AndReturn(3)
do ..ReplayAllMocks()
do $$$AssertEquals(..testedClass.SumOperation(1, 3), 3)
do ..VerifyAllMocks()
]]></Implementation>
</Method>
<UDLText name="T">
<Content><![CDATA[
// --- Tests for ComplexOperation ---
]]></Content>
</UDLText>
<Method name="TestComplexOperationDivide">
<Description>
The mathService.Divide() method isn't even implemented. Yet this test is able
to verify that the status can be properly returned when passed by reference.
The result of 6/2 doesn't even count. The mock is told to return "50".</Description>
<Implementation><![CDATA[
#Dim expectedStatus As %Status = $$$OK
do ..Expect(..mathService.GreaterThan(6, 2)).AndReturn(1)
do ..Expect(..mathService.Divide(6, 2, ..ByRefParam($$$OK, expectedStatus))).AndReturn(50)
do ..ReplayAllMocks()
#Dim status As %Status = $$$OK
do $$$AssertEquals(..testedClass.ComplexOperation(6, 2, .status), 50)
do $$$AssertStatusOK(status)
do ..VerifyAllMocks()
]]></Implementation>
</Method>
<Method name="TestComplexOperationMultiply">
<Implementation><![CDATA[
#Dim expectedStatus As %Status = $$$OK
do ..Expect(..mathService.GreaterThan(6, 2)).AndReturn(0) // <-- Does not even have to be real.
do ..Expect(..mathService.Multiply(6, 2, ..ByRefParam($$$OK, expectedStatus))).AndReturn(-25)
do ..ReplayAllMocks()
#Dim status As %Status = $$$OK
do $$$AssertEquals(..testedClass.ComplexOperation(6, 2, .status), -25)
do $$$AssertStatusOK(status)
do ..VerifyAllMocks()
]]></Implementation>
</Method>
<Method name="TestComplexOperationErrorDivisionByZeroError">
<Implementation><![CDATA[
#Dim expectedStatus As %Status = $$$ERROR("Divide by zero")
do ..Expect(..mathService.GreaterThan(6, 0)).AndReturn(1)
do ..Expect(..mathService.Divide(6, 0, ..ByRefParam($$$OK, expectedStatus))).AndReturn(-1)
do ..ReplayAllMocks()
#Dim status As %Status = $$$OK
do $$$AssertEquals(..testedClass.ComplexOperation(6, 0, .status), -1)
do $$$AssertStatusNotOK(status)
do ..VerifyAllMocks()
]]></Implementation>
</Method>
<Method name="TestComplexOperationUnconsumedError">
<Description>
This tests fails on purpose</Description>
<Implementation><![CDATA[
#Dim expectedStatus As %Status = $$$ERROR("Divide by zero")
do ..Expect(..mathService.Divide(6, 2, ..ByRefParam($$$OK, expectedStatus))).AndReturn(50)
do ..ReplayAllMocks()
// NOTHING
do ..VerifyAllMocks()
]]></Implementation>
</Method>
<Method name="TestComplexOperationUnexpectedCallError">
<Description>
This tests fails on purpose</Description>
<Implementation><![CDATA[
// NO EXPECT
do ..ReplayAllMocks()
#Dim status As %Status = $$$OK
do $$$AssertEquals(..testedClass.ComplexOperation(6, 2, .status), 50)
do $$$AssertStatusNotOK(status)
do ..VerifyAllMocks()
]]></Implementation>
</Method>
<UDLText name="T">
<Content><![CDATA[
// --- Tests for FetchSQLQueryInformation ---
]]></Content>
</UDLText>
<Method name="TestFetchSQLQueryInformation">
<Implementation><![CDATA[
do ..SetMockResultSet("MockDemo.CTestedClass:MyQuery",
..%ClassName(1) _ "||ResultSetMockedDemo")
do ..ReplayAllMocks()
do $$$AssertEquals(..testedClass.FetchSQLQueryInformation("C"), "A+C")
do ..VerifyAllMocks()
]]></Implementation>
</Method>
<XData name="ResultSetMockedDemo">
<Data><![CDATA[
<Query>
<ColumnsNames>,ResultA,ResultB</ColumnsNames>
<Row>A,C</Row>
<Row>A,D</Row>
</Query>
]]></Data>
</XData>
<UDLText name="T">
<Content><![CDATA[
// --- Tests for FetchSQLPrepareInformation ---
]]></Content>
</UDLText>
<Method name="TestFetchSQLPrepareInformation">
<Implementation><![CDATA[
do ..SetMockResultSet("",
..%ClassName(1) _ "||ResultSetMockedDemo")
do ..ReplayAllMocks()
do $$$AssertEquals(..testedClass.FetchSQLPrepareInformation("C"), "|A+C|A+D")
do ..VerifyAllMocks()
]]></Implementation>
</Method>
<Method name="TestFetchSQLPrepareInformationUnconsumedError">
<Description>
This test fails on purpose to show unconsumed SQL Queries</Description>
<Implementation><![CDATA[
do ..SetMockResultSet("",
..%ClassName(1) _ "||ResultSetMockedDemo")
do ..ReplayAllMocks()
// NOTHING
do ..VerifyAllMocks()
]]></Implementation>
</Method>
<Method name="TestPopulateObjectProperties">
<Description>
This tests validates Object passed in mocks</Description>
<Implementation><![CDATA[
// Prepare expected test parameters
set expectedHl7Message = ##class(EnsLib.HL7.Message).%New()
set expectedHl7Message.DocType = "2.6:ADT_A01"
do $$$AssertStatusOK(expectedHl7Message.SetValueAt("Content", "MSH:4"))
set expectedStream = ##class(%Stream.TmpCharacter).%New()
do expectedStream.Write("This is some CONtent")
// Expect them
do ..Expect(..mathService.GetResult(..ByRefParam($$$NULLOREF, expectedHl7Message),
..NotNullObject(##class(EnsLib.XML.SearchTable).%ClassName(1)),
..IsEqualObject(expectedStream))
).AndReturn($$$OK)
do ..ReplayAllMocks()
// Start the test
#Dim hl7Message As EnsLib.HL7.Message = $$$NULLOREF
set searchTable = ##class(EnsLib.XML.SearchTable).%New()
do $$$AssertStatusOK(..testedClass.PopulateObjectProperties(.hl7Message, searchTable))
do $$$AssertEquals(hl7Message.DocType, "2.6:ADT_A01")
do $$$AssertTrue(hl7Message.GetValueAt("MSH:4") = "Content")
do ..VerifyAllMocks()
]]></Implementation>
</Method>
</Class>
</Export>
<?xml version="1.0" encoding="UTF-8"?>
<Export generator="Cache" version="25">
<Class name="MockDemo.UnitTests.CTestTestedClassWithoutInjection">
<Super>Tests.Fw.CUnitTestBase</Super>
<TimeCreated>64377,73873.008945</TimeCreated>
<UDLText name="T">
<Content><![CDATA[
// --- Run test ---
]]></Content>
</UDLText>
<Property name="testedClass">
<Type>MockDemo.CTestedClassWithoutInjection</Type>
<Private>1</Private>
</Property>
<Method name="RunTests">
<ClassMethod>1</ClassMethod>
<Implementation><![CDATA[ do ##super()
]]></Implementation>
</Method>
<Method name="OnBeforeOneTest">
<FormalSpec>testname:%String</FormalSpec>
<ReturnType>%Status</ReturnType>
<Implementation><![CDATA[
set ..testedClass = ##class(MockDemo.CTestedClassWithoutInjection).%New()
quit $$$OK
]]></Implementation>
</Method>
<UDLText name="T">
<Content><![CDATA[
// --- Tests for ComplexSumOperation ---
]]></Content>
</UDLText>
<Method name="TestSumOperation">
<Implementation><![CDATA[ do $$$AssertEquals(..testedClass.SumOperation(1, 2), 3)
]]></Implementation>
</Method>
</Class>
</Export>
<?xml version="1.0" encoding="UTF-8"?>
<Export generator="Cache" version="25">
<Class name="Tests.CTestSuite">
<Super>Tests.Fw.CExecuteTests</Super>
<TimeCreated>64517,31560.257436</TimeCreated>
<Method name="RunTests">
<ClassMethod>1</ClassMethod>
<Implementation><![CDATA[ do ##super()
]]></Implementation>
</Method>
</Class>
</Export>
<?xml version="1.0" encoding="UTF-8"?>
<Export generator="Cache" version="25">
<Class name="Tests.Fw.CExecuteTests">
<Super>%RegisteredObject</Super>
<TimeCreated>63684,52123.001053</TimeCreated>
<Method name="RunTests">
<Description>
Note ACGendron : Fonctionnement hasardeux avant Caché 2014 mais devrait
compiler quand même sans problème !
-----------------------------------------------------------------------------</Description>
<ClassMethod>1</ClassMethod>
<FormalSpec>bIsClass:%Boolean=0</FormalSpec>
<Implementation><![CDATA[
#dim strClassName As %String = ..%ClassName(1)
set:('bIsClass) strClassName = ..%PackageName()
do ##class(Tests.Fw.InterSystems.CManager).RunTest(strClassName)
]]></Implementation>
</Method>
<Method name="EnableUnitTestFramework">
<Description>
See http://localhost:8972/csp/docbook/DocBook.UI.Page.cls?KEY=TUNT_ExampleTestPortal </Description>
<ClassMethod>1</ClassMethod>
<Implementation><![CDATA[
#Dim strOldNS = $ZConvert($NAMESPACE, "L")
ZNSpace "%SYS"
set ^SYS("Security","CSP","AllowPrefix","/csp/"_strOldNS_"/","%UnitTest.") = 1
ZNSpace strOldNS
]]></Implementation>
</Method>
<Method name="DeleteAllOlderTest">
<ClassMethod>1</ClassMethod>
<Implementation><![CDATA[ do ##class(%UnitTest.Result.TestInstance).%DeleteExtent()
]]></Implementation>
</Method>
</Class>
</Export>
<?xml version="1.0" encoding="UTF-8"?>
<Export generator="Cache" version="25">
<Class name="Tests.Fw.CUnitTestBase">
<Description>
Supports :
$$$AssertEquals Returns true if expressions are equal. do $$$AssertEquals(x,y,"x equals y")
$$$AssertNotEquals Returns true if expressions are not equal. do $$$AssertNotEquals(x,y,"x is not equal to y")
$$$AssertStatusOK Returns true if the returned status code is 1. do $$$AssertStatusOK(sc,"Status is OK")
$$$AssertStatusNotOK Returns true if the returned status code is 0. do $$$AssertStatusNotOK(sc,"Status is NotOK")
$$$AssertTrue Returns true if the expression is true. do $$$AssertTrue(x=y,"Expression x=y is true")
$$$AssertNotTrue Returns true if the expression is not true. do $$$AssertNotTrue(x=y,"Expression x=y is not true")
$$$AssertFilesSame Returns true if two files are identical. do $$$AssertFilesSame(output.log,reference.log,"Comparing output.log to reference.log")
$$$LogMessage Writes a log message to the ^UnitTestLog global. do $$$LogMessage("My message")
This class is only a wrapper, to prevent having to inherit a "InterSystems" class in CHS classes.</Description>
<Super>Tests.Fw.InterSystems.CUnitTest,Tests.Fw.CExecuteTests,Tests.Fw.Mock.CMockManager</Super>
<TimeCreated>62883,70697.280223</TimeCreated>
<Parameter name="bSQLTransactionEnabled">
<Type>%Boolean</Type>
<Default>1</Default>
</Parameter>
<Method name="RunTests">
<ClassMethod>1</ClassMethod>
<Implementation><![CDATA[ do ##super(1)
]]></Implementation>
</Method>
<Method name="EnableSuccessfulAssert">
<FormalSpec><![CDATA[bEnable:%Boolean,&bPreviousValue:%Boolean]]></FormalSpec>
<Implementation><![CDATA[
do ..LogMessage("Turning successful Assert " _ $case(bEnable, 1 :"ON", 0 : "OFF"))
set bPreviousValue = ..Manager.bShowSuccessfulAssert
set ..Manager.bShowSuccessfulAssert = bEnable
]]></Implementation>
</Method>
<Method name="GetXDataPayload">
<Description><![CDATA[
Extracts the data of a XData test section into a stream.
If the data is test encapsulated in <CDATA> nodes, they are
removed from the stream content.]]></Description>
<FormalSpec>strXDataName:%String</FormalSpec>
<ReturnType>%Stream.Object</ReturnType>
<Implementation><![CDATA[
set xDataContentStream = ##class(%Stream.TmpCharacter).%New()
// If no class name is given, use the current one.
set:(strXDataName '[ "||") strXDataName = ..%ClassName(1) _ "||" _ strXDataName
set xData = ##class(%Dictionary.CompiledXData).%OpenId(strXDataName)
if ($IsObject(xData))
{
#Dim line As %String = xData.Data.ReadLine()
// If the first line starts by <CDATA>, it is removed, as well as the closing tag.
if (line = "<CDATA></CDATA>")
{
do xDataContentStream.WriteLine("")
}
elseif (line = "<CDATA>")
{
while ('xData.Data.AtEnd)
{
set line = xData.Data.ReadLine()
do:(line '= "</CDATA>") xDataContentStream.WriteLine(line)
}
}
else
{
do xDataContentStream.CopyFrom(xData.Data)
}
}
do xDataContentStream.Rewind()
quit xDataContentStream
]]></Implementation>
</Method>
<Method name="GetHL7FromString">
<FormalSpec>strHL7Message:%String,strMessageSchemaCategory:%String=""</FormalSpec>
<ReturnType>EnsLib.HL7.Message</ReturnType>
<Implementation><![CDATA[
#Dim hl7Message As EnsLib.HL7.Message = ##class(EnsLib.HL7.Message).ImportFromString(strHL7Message)
set srv = ##class(EnsLib.HL7.Service.FileService).%New("Name")
set srv.MessageSchemaCategory = strMessageSchemaCategory
set:(strMessageSchemaCategory = "") srv.MessageSchemaCategory = hl7Message.TypeVersion
do hl7Message.PokeDocType(srv.resolveDocType(hl7Message))
quit hl7Message
]]></Implementation>
</Method>
<Method name="GetHL7FromXData">
<FormalSpec>strXDataName:%String,strMessageSchemaCategory:%String=""</FormalSpec>
<ReturnType>EnsLib.HL7.Message</ReturnType>
<Implementation><![CDATA[ quit ..GetHL7FromString(..GetXDataPayload(strXDataName).Read(1000000), strMessageSchemaCategory)
]]></Implementation>
</Method>
</Class>
</Export>
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
<?xml version="1.0" encoding="UTF-8"?>
<Export generator="Cache" version="25">
<Class name="Tests.Fw.InterSystems.CUnitTestTransaction">
<Description>
*****************************************************
This class comes from InterSystens, provided at the
Global Summit 2015. It was not modified, except for
the packages name.
*****************************************************
This is a class for tests that change transactional data.
It's run within a transaction which is rolled back at the end.</Description>
<Super>Tests.Fw.InterSystems.CUnitTest</Super>
<TimeCreated>62883,71852.951342</TimeCreated>
<Method name="OnBeforeOneTest">
<ReturnType>%Status</ReturnType>
<Implementation><![CDATA[
TStart
Quit $$$OK
]]></Implementation>
</Method>
<Method name="OnAfterOneTest">
<ReturnType>%Status</ReturnType>
<Implementation><![CDATA[
TRollback
Quit $$$OK
]]></Implementation>
</Method>
</Class>
</Export>
<?xml version="1.0" encoding="UTF-8"?>
<Export generator="Cache" version="25">
<Class name="Tests.Fw.Mock.CCustomFunctionParamValidator">
<Super>%RegisteredObject,Tests.Fw.Mock.IMockParamValidator</Super>
<TimeCreated>63890,58067.960125</TimeCreated>
<Property name="objectReference">
<Type>%RegisteredObject</Type>
</Property>
<Property name="strFunctionName">
<Type>%String</Type>
</Property>
<Method name="%OnNew">
<Internal>1</Internal>
<FormalSpec>objectReference:%RegisteredObject,strFunctionName:%String</FormalSpec>
<ReturnType>%Status</ReturnType>
<Implementation><![CDATA[
set ..objectReference = objectReference
set ..strFunctionName = strFunctionName
quit $$$OK
]]></Implementation>
</Method>
<Method name="Validate">
<Internal>1</Internal>
<FormalSpec>object</FormalSpec>
<ReturnType>%Status</ReturnType>
<Implementation><![CDATA[
#dim status As %Status = $$$ERROR("Function or object not defined")
if ($IsObject(..objectReference) && (..strFunctionName '= ""))
{
try
{
do $Method(..objectReference, ..strFunctionName, object)
set status = $$$OK
}
catch
{
set status = $ZERROR
}
}
quit status
]]></Implementation>
</Method>
<Method name="GetAssertDescription">
<Internal>1</Internal>
<FormalSpec>nIndex:%Integer,strMethodName:%String</FormalSpec>
<ReturnType>%String</ReturnType>
<Implementation><![CDATA[
quit "Unable to invoke method """ _ ..strFunctionName _ "()"" on object " _ ..objectReference _
" to validate parameter(" _ nIndex _ ") for method: " _ strMethodName
]]></Implementation>
</Method>
</Class>
</Export>
<?xml version="1.0" encoding="UTF-8"?>