【问题标题】:Unit testing a code that uses Azure Cloud Services对使用 Azure 云服务的代码进行单元测试
【发布时间】:2018-08-16 13:56:49
【问题描述】:

我有一个代码,我必须为其编写单元测试。 由于我是单元测试的新手,因此我正在寻求您的帮助。 我想测试 IsAdmin 方法,它将 CloudTableClient 和字符串作为输入,但我真的不知道如何处理它。 我尝试了下面的代码,但它失败并显示消息:

消息:测试方法 UnitTestMatan.UserControllerTests.IsAdmin_NotOnTheList_ReturnFalse 抛出异常: System.MethodAccessException:尝试通过方法'MatanWebServer.Controllers.UserController.IsAdmin(Microsoft.WindowsAzure.Storage.Table.CloudTableClient,System.String)'访问方法'Microsoft.WindowsAzure.Storage.Table.CloudTable.ExecuteQuery(Microsoft.WindowsAzure .Storage.Table.TableQuery, Microsoft.WindowsAzure.Storage.Table.TableRequestOptions, Microsoft.WindowsAzure.Storage.OperationContext)' 失败。

基本上,我要测试的第一件事是 IsAdmin 在收到不在表中或只是随机字符串的电子邮件时将返回 false。 在测试部分,我想使用在 UserController 中生成的表,而不是新建一个。

以下是我需要测试的类:

using Microsoft.WindowsAzure.Storage;
using Microsoft.WindowsAzure.Storage.Table;
using ParseMatanDataWebJob;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web.Http;

namespace MatanWebServer.Controllers
{    
    public class UserController : ApiController
    {
        // GET: api/user
        [AdminAuthorizeAttribute]
        public IEnumerable<string> Get()
        {
            CloudStorageAccount storageAccount = CloudStorageAccount.Parse("DefaultEndpointsProtocol=http;AccountName=matandata;AccountKey=myconnectionstringthatidontwantyoutoknow");
            CloudTableClient tableClient = storageAccount.CreateCloudTableClient();

            IEnumerable<string> EmailsList = GetAllEmails(tableClient);

            return (EmailsList);
        }

        // GET: api/user/5
        [UserAuthorizeAttribute]        
        public ReturnObjectValuescontroller Get(string email)
        {            
            // Retrieve the storage account from the connection string.p
            CloudStorageAccount storageAccount = CloudStorageAccount.Parse("myconnectionstringthatidontwantyoutoknow");

            // Create the table client.
            CloudTableClient tableClient = storageAccount.CreateCloudTableClient();

            var result = new ReturnObjectValuescontroller();

            result.isAdmin = IsAdmin(tableClient, email);

            // Create the CloudTable object that represents the "people" table.
            CloudTable donationsTable = tableClient.GetTableReference("userdonationsdata");

            // Create the table query.
            TableQuery<DonationData> userDonationsQuery = new TableQuery<DonationData>().Where(
            TableQuery.GenerateFilterCondition("PartitionKey", QueryComparisons.Equal, email.ToString()));

            var DonationsResult = donationsTable.ExecuteQuery(userDonationsQuery).ToList();

            result.DonationsList = DonationsResult;

            return result;
        }

        public static bool IsAdmin(CloudTableClient tableClient, string email)
        {
            CloudTable AdminsTable = tableClient.GetTableReference("matanadminusers");

            TableQuery AdminsListQuery = new TableQuery();

            AdminsListQuery.SelectColumns = new List<string>() { "AdminEmail" };

            var TableEntityAdminsList = AdminsTable.ExecuteQuery(AdminsListQuery).ToList();

            List<string> AdminsEmailList = new List<string>();
            foreach (var item in TableEntityAdminsList)
            {
                AdminsEmailList.Add(item.Properties["AdminEmail"].StringValue);
            }

            return (AdminsEmailList.Contains(email, StringComparer.OrdinalIgnoreCase));
        }

        private IEnumerable<string> GetAllEmails(CloudTableClient tableClient)
        {            
            CloudTable donationsTable = tableClient.GetTableReference("useremails");

            TableQuery EmailsListQuery = new TableQuery();

            EmailsListQuery.SelectColumns = new List<string>() { "RowKey" };

            var DynamicEntityEmailsList = donationsTable.ExecuteQuery(EmailsListQuery).ToList();

            List<string> EmailList = new List<string>();
            foreach (var item in DynamicEntityEmailsList)
            {
                EmailList.Add(item.RowKey);
            }
            return EmailList.Distinct();
        }

        // POST: api/Rony
        public void Post([FromBody]string value)
        {
        }

        // PUT: api/Rony/5
        public void Put(int id, [FromBody]string value)
        {
        }

        // DELETE: api/Rony/5
        public void Delete(int id)
        {
        }
    }
}

这是我为 IsAdmin 尝试的测试:

using System;
using MatanWebServer.Controllers;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Microsoft.WindowsAzure.Storage;
using Microsoft.WindowsAzure.Storage.Table;

namespace UnitTestMatan
{
    [TestClass]
    public class UserControllerTests
    {
        [TestMethod]
        public void IsAdmin_NotOnTheList_ReturnFalse()
        {
            var userController = new UserController();

            CloudStorageAccount storageAccount = CloudStorageAccount.Parse("myconnectionstringthatidontwantyoutoknow");
            CloudTableClient tableClient = storageAccount.CreateCloudTableClient();
            var result = UserController.IsAdmin(tableClient, "str");

            Assert.IsFalse(result);       
        }
    }
}

【问题讨论】:

  • 您可能想查看moq

标签: c# visual-studio azure unit-testing azure-cloud-services


【解决方案1】:

这更像是“令人难以置信”关于使用接口的答案的后续。

举个例子:

我有一个名为 Person 的类,它需要将一些信息发送到数据库。然后,我将把它放在另一个类中,而不是在 Person 类中使用该逻辑。此类负责包含 Person 类与数据库通信所需的所有逻辑,此类通常称为数据访问层 (DAL) 或存储库。好的,举个例子:

Person 类:

public Person(IDALPerson d)
{
        dal = d;           
}

private IDALPerson dal;

public void SendOrder()
{            
    (Other logic...)
    dal.SendOrder("Bananas");
}

所以这里 Person 类通过它的构造器从程序的另一个地方获取这个 DAL(依赖),这是一种依赖注入。这个 DAL 实现了 IDALPerson 接口,所以具体的 DAL 类可以是:

public class PersonDAL : IDALPerson
{
    public void SendOrder()
    {
        (logic for adding order)
    }
}

然后在执行测试时,您可以创建 IDALPerson 的模拟对象,这样在测试时,您的 Person 类实际上不使用数据库,然后您可以使用 Moq 框架获取一些信息,如“increddibelly”所述" 从而在不依赖数据库的情况下测试 SendOrder 函数。 您的测试可能如下所示:

    [Test]
    public void SendOrderTest()
    {
        //Arrange
        Mock<IDALPerson> MockDAL = new Mock<IDALPerson>();
        Person p = new Person(MockDAL);         

        //Act
        p.SendOrder();

        //Assert
        //Assert something.
   }

【讨论】:

  • 谢谢!如果我必须使用来自 Azure dll 的 ClientTable 类型,我仍然不确定我应该如何将它实现到我的代码中?
  • 我明白了,不幸的是我对你提到的东西了解不多,只是使用接口的通用概念......
【解决方案2】:

对于任何和所有单元测试(队列雪花边缘情况),您需要将代码依赖的组件替换为虚拟/模拟/假对象。 我们为此使用了一个出色的库 Moq。

说你的代码需要一个

IStuffCreator 

有一个

Stuff CreateStuff()

方法,你可以控制这个外部组件的结果如下:

var sc = Mock<IStuffCreator>();
sc.Setup(x => x.CreateStuff()).Returns(new Stuff { Id = 1} );

MyClass classUnderTest = new classUnderTest(sc.Object);

任何触及 CreateStuff 方法的 classUnderTest 调用都将获得一个 ID 为 1 的新 Stuff,因为您以这种方式设置了假组件。

如果你需要 azure 来告诉你的代码有一个管理员,你可以做同样的事情。

var iauth = new Mock<IAzureAuthenticator>()
iauth.Setup(x => x.IsAdmin(It.IsAny<TableClient>, It.IsAny<string>())).Returns(true);

或者如果您只想设置特定案例:

var myTableClient = new TableClient { Id = 123 };
iauth.Setup(x => x.IsAdmin(myTableClient, It.IsAny<string>)).Returns(true);

但是您可能希望将所有其他情况设置为返回 false,以便您绝对确定您的测试值会改变行为。

iauth.Setup(x => x.IsAdmin(It.IsAny<TableClient>(), It.IsAny<string>)).Returns(false);
iauth.Setup(x => x.IsAdmin(myTableClient, It.IsAny<string>)).Returns(true);

希望它这么简单 - 但我相信你明白这一点。

如果这对您不起作用,这通常意味着您不依赖于接口,而是依赖于显式实现。由于依赖接口而不是实现是一个可靠的改进(呵呵),你现在有一个很好的机会来改进你的代码;)

【讨论】:

  • 谢谢!现在当我在我的测试类中实现它时,它应该是这样的: var sc = new Mock(); sc.Setup(x => x.IsAdmin(tableClient, "str")).Returns(false);我有点理解你在说什么,但仍然不知道如何在我的测试中实现它,因为 IsAdmin 获取了一个 TableClient 和一个字符串,那么我将如何创建一个包含内容的 TableClient?
  • 对于个人资料显示他们在微软工作的人来说,这似乎是一个非常可疑的问题。 new TableClient { content = "inside" } 怎么样?我会更新我的答案,如果它对你有用,也许将它标记为对你有用的答案:)
猜你喜欢
  • 2011-04-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-05-10
  • 1970-01-01
  • 1970-01-01
  • 2016-09-24
  • 1970-01-01
相关资源
最近更新 更多