Loading...

使用Android Studio在第一次导入项目或者配置完Kotlin后,会一直Build,因为需要下载一些项目中配置的依赖和gradle插件等,有的需要翻墙,由于国内的网络环境很容易超时。可以使用下面的方法解决的问题:

使用阿里云的国内镜像仓库地址,就可以快速的下载需要的文件

修改项目根目录下的文件

buildscript {
    repositories {
        //加入下面这句
        maven{ url 'http://maven.aliyun.com/nexus/content/groups/public/'}
    }
}

allprojects {
    repositories {
        //加入下面这句
        maven{ url 'http://maven.aliyun.com/nexus/content/groups/public/'}
    }
}
版权所有©转载必须以链接形式注明作者和原始出处:江湖源码 » 解决Android Studio配置完依赖等下载慢的问题

当我们升级完成AS后,相应的gradle也会需要升级,使用新版本的AS打开项目时,项目下的gradle/wrapper/gradle-wrapper.properties文件里面的gradle版本会更新成当前AS所匹配的gradle版本。此时AS就一直在刷新项目,其实是在进行蜗牛般的速度下载着gradle。

以我现在的AS为例,我把AS升级到2.3后,相应的gradle版本是gradle-2.14.1,可以通过查看文件gradle-wrapper.properties得知你所需的gradle版本。

1.使用下载工具下载gradle
gradle的官网下载地址是 https://gradle.org/releases,打开网址后下载complete版本的gradle。复制下载链接到迅雷下载即可。

2.替换本地gradle
完全关闭AS,包括正在下载gradle的进程也需要关闭。进入到本地的gradle存储目录,我的MAC是/Users/Seven/.gradle/wrapper/dists,linux系统的话应该是在个人用户目录下。

把gradle-2.14.1-all.zip文件复制到 gradle-2.14.1-all/8bnwg5hd3w55iofp58khbp6yv 目录下,同时把该目录下的其他文件删除掉。这个8bnwg5hd3w55iofp58khbp6yv是由AS自动生成的,不能更改。

重新打开AS,会自动解压并生成文件。至此gradle更新完成。

作者:SevChen
链接:https://www.jianshu.com/p/6a6c3a07b39f
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

版权所有©转载必须以链接形式注明作者和原始出处:江湖源码 » 解决AndroidStudio下载gradle慢的问题

单元测试可以有效的可以在编码、设计、调试到重构等多方面显著提升我们的工作效率和质量。github上可供参考和学习的各种开源项目众多,NopCommerce、Orchard等以及微软的asp.net mvc、entity framework相关多数项目都可以作为学习单元测试的参考。单元测试之道(C#版本)、.NET单元测试艺术和C#测试驱动开发都是不错的学习资料。

1.单元测试的好处
(1)单元测试帮助设计

单元测试迫使我们从关注实现转向关注接口,编写单元测试的过程就是设计接口的过程,使单元测试通过的过程是我们编写实现的过程。我一直觉得这是单元测试最重要的好处,让我们关注的重点放在接口上而非实现的细节。

(2)单元测试帮助编码

应用单元测试会使我们主动消除和减少不必要的耦合,虽然出发点可能是为了更方便的完成单元测试,但结果通常是类型的职责更加内聚,类型间的耦合显著降低。这是已知的提升编码质量的有效手段,也是提升开发人员编码水平的有效手段。

(3)单元测试帮助调试

应用了单元测试的代码在调试时可以快速定位问题的出处。

(4)单元测试帮助重构

对于现有项目的重构,从编写单元测试开始是更好的选择。先从局部代码进行重构,提取接口进行单元测试,然后再进行类型和层次级别的重构。

单元测试在设计、编码和调试上的作用足以使其成为软件开发相关人员的必备技能。

2.应用单元测试
单元测试不是简单的了解使用类似XUnit和Moq这样的测试和模拟框架就可以使用了,首先必须对我们要编写的代码有足够的了解。通常我们把代码看成一些静态的互相关联的类型,类型之间的依赖使用接口,实现类实现接口,在运行时通过自定义工厂或使用依赖注入容器管理。一个单元测试通常是在一个方法中调用要测试的方法或属性,通过使用Assert断言对方法或属性的运行结果进行检测,通常我们需要编写的测试代码有以下几种。

(1)测试领域层

领域层由POCO组成,可以直接测试领域模型的公开行为和属性。

(2)测试应用层

应用层主要由服务接口和实现组成,应用层对基础设施组件的依赖以接口方式存在,这些基础设施的接口通过Mock方式模拟。

(3)测试表示层

表示层对应用层的依赖表现在对服务接口的调用上,通过Mock方式获取依赖接口的实例。

(4)测试基础设施层

基础设施层的测试通常涉及到配置文件、Log、HttpContext、SMTP等系统环境,通常需要使用Mock模式。

(5)使用单元测试进行集成测试

首先系统之间通过接口依赖,通过依赖注入容器获取接口实例,在配置依赖时,已经实现的部分直接配置,伪实现的部分配置为Mock框架生成的实例对象。随着系统的不断实现,不断将依赖配置的Mock对象替换为实现对象。

3.使用Assert判断逻辑行为正确性
Assert断言类是单元测试框架中的核心类,在单元测试的方法中,通过Assert类的静态方法对要测试的方法或属性的运行结果进行校验来判断逻辑行为是否正确,Should方法通常是以扩展方法形式提供的Assert的包装。

(1)Assert断言

如果你使用过System.Diagnostics.Contracts.Contract的Assert方法,那么对XUnit等单元测试框架中提供的Assert静态类会更容易,同样是条件判断,单元测试框架中的Assert类提供了大量更加具体的方法如Assert.True、Assert.NotNull、Assert.Equal等便于条件判断和信息输出。

(2)Should扩展方法

使用Should扩展方法既减少了参数的使用,又增强了语义,同时提供了更友好的测试失败时的提示信息。Xunit.should已经停止更新,Should组件复用了Xunit的Assert实现,但也已经停止更新。Shouldly组件则使用了自己实现,是目前仍在更新的项目,structuremap在单元测试中使用Shouldly。手动对Assert进行包装也很容易,下面的代码提取自 NopComnerce 3.70 中对NUnit的Assert的自定义扩展方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
namespace Nop.Tests
{
    public static class TestExtensions
    {
        public static T ShouldNotNull<T>(this T obj)
        {
            Assert.IsNull(obj);
            return obj;
        }
 
        public static T ShouldNotNull<T>(this T obj, string message)
        {
            Assert.IsNull(obj, message);
            return obj;
        }
 
        public static T ShouldNotBeNull<T>(this T obj)
        {
            Assert.IsNotNull(obj);
            return obj;
        }
 
        public static T ShouldNotBeNull<T>(this T obj, string message)
        {
            Assert.IsNotNull(obj, message);
            return obj;
        }
 
        public static T ShouldEqual<T>(this T actual, object expected)
        {
            Assert.AreEqual(expected, actual);
            return actual;
        }
 
        ///<summary>
        /// Asserts that two objects are equal.
        ///</summary>
        ///<param name="actual"></param>
        ///<param name="expected"></param>
        ///<param name="message"></param>
        ///<exception cref="AssertionException"></exception>
        public static void ShouldEqual(this object actual, object expected, string message)
        {
            Assert.AreEqual(expected, actual);
        }
 
        public static Exception ShouldBeThrownBy(this Type exceptionType, TestDelegate testDelegate)
        {
            return Assert.Throws(exceptionType, testDelegate);
        }
 
        public static void ShouldBe<T>(this object actual)
        {
            Assert.IsInstanceOf<T>(actual);
        }
 
        public static void ShouldBeNull(this object actual)
        {
            Assert.IsNull(actual);
        }
 
        public static void ShouldBeTheSameAs(this object actual, object expected)
        {
            Assert.AreSame(expected, actual);
        }
 
        public static void ShouldBeNotBeTheSameAs(this object actual, object expected)
        {
            Assert.AreNotSame(expected, actual);
        }
 
        public static T CastTo<T>(this object source)
        {
            return (T)source;
        }
 
        public static void ShouldBeTrue(this bool source)
        {
            Assert.IsTrue(source);
        }
 
        public static void ShouldBeFalse(this bool source)
        {
            Assert.IsFalse(source);
        }
 
        /// <summary>
        /// Compares the two strings (case-insensitive).
        /// </summary>
        /// <param name="actual"></param>
        /// <param name="expected"></param>
        public static void AssertSameStringAs(this string actual, string expected)
        {
            if (!string.Equals(actual, expected, StringComparison.InvariantCultureIgnoreCase))
            {
                var message = string.Format("Expected {0} but was {1}", expected, actual);
                throw new AssertionException(message);
            }
        }
    }
}

4.使用伪对象
伪对象可以解决要测试的代码中使用了无法测试的外部依赖问题,更重要的是通过接口抽象实现了低耦合。例如通过抽象IConfigurationManager接口来使用ConfigurationManager对象,看起来似乎只是为了单元测试而增加更多的代码,实际上我们通常不关心后去的配置是否是通过ConfigurationManager静态类读取的config文件,我们只关心配置的取值,此时使用IConfigurationManager既可以不依赖具体的ConfigurationManager类型,又可以在系统需要扩展时使用其他实现了IConfigurationManager接口的实现类。

使用伪对象解决外部依赖的主要步骤:

(1)使用接口依赖取代原始类型依赖。

(2)通过对原始类型的适配实现上述接口。

(3)手动创建用于单元测试的接口实现类或在单元测试时使用Mock框架生成接口的实例。

手动创建的实现类完整的实现了接口,这样的实现类可以在多个测试中使用。可以选择使用Mock框架生成对应接口的实例,只需要对当前测试需要调用的方法进行模拟,通常需要根据参数进行逻辑判断,返回不同的结果。无论是手动实现的模拟类对象还是Mock生成的伪对象都称为桩对象,即Stub对象。Stub对象的本质是被测试类依赖接口的伪对象,它保证了被测试类可以被测试代码正常调用。

解决了被测试类的依赖问题,还需要解决无法直接在被测试方法上使用Assert断言的情况。此时我们需要在另一类伪对象上使用Assert,通常我们把Assert使用的模拟对象称为模拟对象,即Mock对象。Mock对象的本质是用来提供给Assert进行验证的,它保证了在无法直接使用断言时可以正常验证被测试类。

Stub和Mock对象都是伪对象,即Fake对象。

Stub或Mock对象的区分明白了就很简单,从被测试类的角度讲Stub对象,从Assert的角度讲Mock对象。然而,即使不了解相关的含义和区别也不会在使用时产生问题。比如测试邮件发送,我们通常不能直接在被测试代码上应用Assert,我们会在模拟的STMP服务器对象上应用Assert判断是否成功接收到邮件,这个SMTPServer模拟对象就是Mock对象而不是Stub对象。比如写日志,我们通常可以直接在ILogger接口的相关方法上应用Assert判断是否成功,此时的Logger对象即是Stub对象也是Mock对象。

5.单元测试常用框架和组件
(1)单元测试框架。

XUnit是目前最为流行的.NET单元测试框架。NUnit出现的较早被广泛使用,如nopCommerce、Orchard等项目从开始就一直使用的是NUnit。XUnit目前是比NUnit更好的选择,从github上可以看到asp.net mvc等一系列的微软项目使用的就是XUnit框架。

(2)Mock框架

Moq是目前最为流行的Mock框架。Orchard、asp.net mvc等微软项目使用Moq。nopCommerce使用Rhino Mocks。NSubstitute和FakeItEasy是其他两种应用广泛的Mock框架。

(3)邮件发送的Mock组件netDumbster

可以通过nuget获取netDumbster组件,该组件提供了SimpleSmtpServer对象用于模拟邮件发送环境。

通常我们无法直接对邮件发送使用Assert,使用netDumbster我们可以对模拟服务器接收的邮件应用Assert。

1
2
3
4
5
6
7
8
9
10
11
12
13
public void SendMailTest()
{
    SimpleSmtpServer server = SimpleSmtpServer.Start(25);
    IEmailSender sender = new SMTPAdapter();
    sender.SendMail("sender@here.com", "receiver@there.com", "subject", "body");
    Assert.Equal(1, server.ReceivedEmailCount);
    SmtpMessage mail = (SmtpMessage)server.ReceivedEmail[0];
    Assert.Equal("sender@here.com", mail.Headers["From"]);
    Assert.Equal("receiver@there.com", mail.Headers["To"]);
    Assert.Equal("subject", mail.Headers["Subject"]);
    Assert.Equal("body", mail.MessageParts[0].BodyData);
    server.Stop();
}

(4)HttpContext的Mock组件HttpSimulator

同样可以通过nuget获取,通过使用HttpSimulator对象发起Http请求,在其生命周期内HttContext对象为可用状态。

由于HttpContext是封闭的无法使用Moq模拟,通常我们使用如下代码片断:

1
2
3
4
5
6
7
8
9
private HttpContext SetHttpContext()
{
    HttpRequest httpRequest = new HttpRequest("", "http://mySomething/", "");
    StringWriter stringWriter = new StringWriter();
    HttpResponse httpResponse = new HttpResponse(stringWriter);
    HttpContext httpContextMock = new HttpContext(httpRequest, httpResponse);
    HttpContext.Current = httpContextMock;
    return HttpContext.Current;
}

使用HttpSimulator后我们可以简化代码为:

1
2
3
4
using (HttpSimulator simulator = new HttpSimulator())
{
 
}

这对使用IoC容器和EntityFramework的程序的DbContext生命周期的测试十分重要,DbContext的生命周期必须和HttpRequest一致,因此对IoC容器进行生命周期的测试是必须的。

6.使用单元测试的难处
(1)不愿意付出学习成本和改变现有开发习惯。

(2)没有思考的习惯,错误的把单元测试当框架学。

(3)在项目后期才应用单元测试,即获取不到单元测试的好处又因为代码的测试不友好对单元测试产生误解。

(4)拒绝考虑效率、扩展性和解耦,只考虑数据和功能的实现。

版权所有©转载必须以链接形式注明作者和原始出处:江湖源码 » ASP.NET 系列:单元测试

Entity Framework是C#开发中最常见的ORM工具。默认Entity Framework只提供支持MSSQL的provider factory 。但是开发者开源贡献了对SQLite、MySql以及Access等的支持。

JetEntityFrameworkProvider

JetEntityFrameworkProvider为Access数据库文件兼容Entity Framework提供了相应的 Provider 。在nuget中直接搜索JetEntityFrameworkProvider即可安装该工具。虽然大多数操作最终是EntityFramework完成因此不需要什么变化,而在数据库连接等方面还是有些不同和需要注意的地方。

数据库连接

官方的资源并不多,只提供了一两个简单的 操作视频

其中使用的 connectionString 为在App.config中或Web.config中预定义的以供DbContext实例化时引用。

1
2
3
<connectionStrings>  
    <add name="DefaultConnection" connectionString="Provider=Microsoft.ACE.OleDb.12.0;Data Source=你的mdb或accdb文件绝对路径" providerName="JetEntityFrameworkProvider" />
</connectionStrings>

这样你在实例化自定义的 DbContext 子类时直接 base("name=DefaultConnection") 即可建立数据库连接。

但是我个人不喜欢在配置文件中写死配置,我更希望使用 base(existingConnection, contextOwnsConnection) 这种DbContext构造模式,所以需要先直接生成一个 DbConnection ,这里具体的就是 OleDbConnection 而不是 SqlConnection 了。

按照下面的方式直接使用 DbProviderFactory 创建连接:

1
2
3
var dbConnectionString = "Provider=Microsoft.ACE.OleDb.12.0;Data source=Access文件绝对路径;Persist Security Info=False";  
var conn = DbProviderFactories.GetFactory("JetEntityFrameworkProvider").CreateConnection();  
conn.ConnectionString = dbConnectionString;

Code First或DB First

默认地,JetEntityFrameworkProvider只支持code first模式。即你先写好模型,然后根据模型生成数据库。

但是我的需求是用EF读取已存在的Access数据库文件,即DB First模式,这时使用EF读取Access数据库文件会报错提示数据库已存在。

通过code first模式的测试发现:

JetEntityFrameworkProvider会创建一个名为 __MigrationHistory 的表,字段如下:

1
2
3
4
MigrationId - text格式  
ContextKey - memo格式  
Model - OleObject格式  
ProductVersion - text格式

MigrationId值的例子为 201612281720088_InitialCreate

ContextKey应该是自定义的DbContext类的namespace加类名的格式

Model是二进制的数据无法查看

ProductVersion包含了Entity Framework的版本号和JetEntityFrameworkProvider的版本号

因此我尝试在EF连接之前用普通SQL query方式插入记录:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var dbConnectionString = "Provider=Microsoft.ACE.OleDb.12.0;Data source=Access文件绝对路径;Persist Security Info=False";  
conn = new OleDbConnection(dbConnectionString);  
conn.Open();  
var exists = conn.GetSchema("Tables", new string[4] { null, null, "__MigrationHistory", "TABLE" }).Rows.Count > 0;
 
if(!exists)  
    {
         OleDbCommand cmd = new OleDbCommand("CREATE TABLE __MigrationHistory([MigrationId] TEXT, [ContextKey] MEMO, [Model] OleObject, [ProductVersion] TEXT)", conn);
          cmd.ExecuteNonQuery();
 
          cmd = new OleDbCommand("INSERT INTO __MigrationHistory(MigrationId, ContextKey, ProductVersion) VALUES('" + DateTime.Now.ToString("yyyyMMddHHmmssfff") + "', '" + typeof(ATOrionContext).Namespace + ".ATOrionContext', '6.1.3-40302')", conn);
           cmd.ExecuteNonQuery();
 
    }

执行如上操作后确实能够利用EF连接已存在模型对应数据表的Access数据库文件了,但是执行查询操作仍然会报错 buffer is null

经过Debug发现此buffer指的就是插入记录的Model字段,我们插入时没有提供值,因为它是一个二进制数据,内容生成方式未知。

尝试注释掉插入记录的操作,只添加__MigrationHistory表,而结果也令人兴奋,可以通过模型验证并且能够使用EF进行DB First模式的数据开发模式了。

版权所有©转载必须以链接形式注明作者和原始出处:江湖源码 » c# 使用Entity Framework操作Access数据库

的元数据
wp_comments:存储评论
wp_links:存储友情链接(Blogroll)
wp_options:存储WordPress系统选项和插件、主题配置
wp_postmeta:存储文章(包括页面、上传文件、修订)的元数据
wp_posts:存储文章(包括页面、上传文件、修订)
wp_terms:存储每个目录、标签
wp_term_relationships:存储每个文章、链接和对应分类的关系
wp_term_taxonomy:存储每个目录、标签所对应的分类
wp_usermeta:存储用户的元数据
wp_users:存储用户

在WordPress的数据库结构中,存储系统选项和插件配置的wp_options表是比较独立的结构,在后文中会提到,它采用了key-value模式存储,这样做的好处是易于拓展,各个插件都可以轻松地在这里存

储自己的配置。

post,comment,user 则是三个基本表加上拓展表的组合。以wp_users为例,wp_users已经存储了每个用户会用到的基本信息,比如 login_name、display_name、 password、email等常用信息,但如果

我们还要存储一些不常用的数据,最好的做法不是去在表后加上一列,去破坏默认的表结构,而是将数据存在wp_usermeta中。wp_usermeta这个拓展表和wp_options表有类似的结构,我们可以在这里存

储每个用户的QQ号码、手机号码、登录WordPress后台的主题选项等等。

比较难以理解的是term,即wp_terms、wp_term_relationships、wp_term_taxonomy。在WordPress的系统里,我们常见的分类有文章的分类、链接的分类,实际上还有TAG,它也是一种特殊的分类方式,

我们甚至还可以创建自己的分类方法。WordPress将所有的分类及分类方法、对应结构都记录在这三个表中。wp_terms记录了每个分类的名字以及基本信息,如本站分为“WordPress开发”、“WPCEO插件

”等,这里的分类指广义上的分类,所以每个TAG也是一个“分类”。wp_term_taxonomy记录了每个分类所归属的分类方法,如“WordPress开发”、“WPCEO插件”是文章分类(category),放置友情链

接的“我的朋友”、“我的同事”分类属于友情链接分类(link_category)。wp_term_relationships记录了每个文章(或链接)所对应的分类方法。

庆幸的是,关于term的使用,WordPress中相关函数的使用方法还是比较清晰明了,我们就没必要纠结于它的构造了。

在上文中我们已经介绍了WordPress数据库中各个表的作用,本文将继续介绍每个表中每个列的作用。WordPress官方文档已经有比较详细的表格,本文仅对常用数据进行介绍。

wp_commentmeta
meta_id:自增唯一ID
comment_id:对应评论ID
meta_key:键名
meta_value:键值

wp_comments
comment_ID:自增唯一ID
comment_post_ID:对应文章ID
comment_author:评论者
comment_author_email:评论者邮箱
comment_author_url:评论者网址
comment_author_IP:评论者IP
comment_date:评论时间
comment_date_gmt:评论时间(GMT+0时间)
comment_content:评论正文
comment_karma:未知
comment_approved:评论是否被批准
comment_agent:评论者的USER AGENT
comment_type:评论类型(pingback/普通)
comment_parent:父评论ID
user_id:评论者用户ID(不一定存在)

wp_links
link_id:自增唯一ID
link_url:链接URL
link_name:链接标题
link_image:链接图片
link_target:链接打开方式
link_description:链接描述
link_visible:是否可见(Y/N)
link_owner:添加者用户ID
link_rating:评分等级
link_updated:未知
link_rel:XFN关系
link_notes:XFN注释
link_rss:链接RSS地址

wp_options
option_id:自增唯一ID
blog_id:博客ID,用于多用户博客,默认0
option_name:键名
option_value:键值
autoload:在WordPress载入时自动载入(yes/no)

wp_postmeta
meta_id:自增唯一ID
post_id:对应文章ID
meta_key:键名
meta_value:键值

wp_posts
ID:自增唯一ID
post_author:对应作者ID
post_date:发布时间
post_date_gmt:发布时间(GMT+0时间)
post_content:正文
post_title:标题
post_excerpt:摘录
post_status:文章状态(publish/auto-draft/inherit等)
comment_status:评论状态(open/closed)
ping_status:PING状态(open/closed)
post_password:文章密码
post_name:文章缩略名
to_ping:未知
pinged:已经PING过的链接
post_modified:修改时间
post_modified_gmt:修改时间(GMT+0时间)
post_content_filtered:未知
post_parent:父文章,主要用于PAGE
guid:未知
menu_order:排序ID
post_type:文章类型(post/page等)
post_mime_type:MIME类型
comment_count:评论总数

wp_terms
term_id:分类ID
name:分类名
slug:缩略名
term_group:未知

wp_term_relationships
object_id:对应文章ID/链接ID
term_taxonomy_id:对应分类方法ID
term_order:排序

wp_term_taxonomy
term_taxonomy_id:分类方法ID
term_id:taxonomy:分类方法(category/post_tag)
description:未知
parent:所属父分类方法ID
count:文章数统计

wp_usermeta
umeta_id:自增唯一ID
user_id:对应用户ID
meta_key:键名
meta_value:键值

wp_users
ID:自增唯一ID
user_login:登录名
user_pass:密码
user_nicename:昵称
user_email:Email
user_url:网址
user_registered:注册时间
user_activation_key:激活码
user_status:用户状态
display_name:显示名称

版权所有©转载必须以链接形式注明作者和原始出处:江湖源码 » WordPress数据库及各表结构分析