阅读更多
现在很多人在开发iOS时都使用ReactiveCocoa,它是一个函数式和响应式编程的框架,使用Signal来代替KVO、Notification、Delegate和Target-Action等传递消息和解决对象之间状态与状态的依赖过多问题。但很多时候使用它之后,如何编写单元测试来验证程序是否正确呢?下面首先了解MVVM架构,然后通过一个例子来讲述我如何在RAC(ReactiveCocoa简称)中使用Kiwi来编写单元测试。

MVVM架构



在MVVM架构中,通常都将view和view controller看做一个整体。相对于之前MVC架构中view controller执行很多在view和model之间数据映射和交互的工作,现在将它交给view model去做。

至于选择哪种机制来更新view model或view是没有强制的,但通常我们都选择ReactiveCocoa。ReactiveCocoa会监听model的改变然后将这些改变映射到view model的属性中,并且可以执行一些业务逻辑。

举个例子来说,有一个model包含一个dateAdded的属性,我想监听它的变化然后更新view model的dateAdded属性。但model的dateAdded属性的数据类型是NSDate,而view model的数据类型是NSString,所以在view model的init方法中进行数据绑定,但需要数据类型转换。示例代码如下:
RAC(self,dateAdded) = [RACObserve(self.model,dateAdded) map:^(NSDate*date){ 
    return [[ViewModel dateFormatter] stringFromDate:date];
}];

ViewModel调用dateFormatter进行数据转换,且方法dateFormatter可以复用到其他地方。然后view controller监听view model的dateAdded属性且绑定到label的text属性。
RAC(self.label,text) = RACObserve(self.viewModel,dateAdded);  

现在我们抽象出日期转换到字符串的逻辑到view model,使得代码可以测试和复用,并且帮view controller瘦身。

登录情景



如图所示,这是一个简单的登录界面:有用户名和密码的两个输入框,一个登录按钮。用户输入完用户名和密码后,点击登录按钮后,成功登录。但这里有限制条件:用户名必须满足邮件的格式和密码长度必须在6位以上。当同时满足这两个条件后才能点击按钮,否则按钮是不可点击的。大家可以从Github中下载实例代码。

首先我们先画界面,我定义一个LoginView,将画登录界面的责任都交给它。然后在LoginViewController中的viewDidLoad方法调用buildViewHierarchy加载它。
#pragma mark - Lifecycle
- (void)viewDidLoad {
    [super viewDidLoad];

    // build view hierarchy
    [self buildViewHierarchy];
    // bind data
    [self bindData];
    // handle events
    [self handleEvents];
}

- (void)buildViewHierarchy
{
    [self.view addSubview:self.rootView];
    [self.rootView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.edges.equalTo(self.view);
    }];
}

接下来我们要思考UI如何交互和如何设计和实现哪些类来处理。由于用户名和密码要同时满足验证格式时才能点击登录按钮,所以需要时刻监听usernameTextField和passwordTextField的text属性,对于处理UI交互、数据校验以及转换都交给MVVM架构中ViewModel来处理。于是定义一个LoginViewModel,并继承RVMViewModel,这个RVMViewModel有个active属性来表示viewModel是否处于活跃状态,当active是YES时,更新或显示UI。当active是NO时,不更新或隐藏UI。
@interface LoginViewModel : RVMViewModel

#pragma mark - UI state
/*
 @brief 用户名
 */
@property (copy, nonatomic) NSString *username;
/*
 @brief 密码
 */
@property (copy, nonatomic) NSString *password;

#pragma mark - Handle events
/*
 @brief 处理用户民和密码是否有效才能点击按钮以及登陆事件
 */
@property (nonatomic, strong) RACCommand *loginCommand;

#pragma mark - Methods
- (RACSignal *)isValidUsernameAndPasswordSignal;

@end

上面还有一个loginCommand属性和isValidUsernameAndPasswordSignal方法等下会详细介绍。定义LoginViewModel类后,在LoginViewController以组合和委托的方式来使用LoginViewModel并使用Lazy Initialization来初始化它。
@interface LoginViewController ()

#pragma mark - View model
@property (strong, nonatomic) LoginViewModel *loginViewModel;

@end

@implementation LoginViewController

#pragma mark - Custom Accessors
- (LoginViewModel *)loginViewModel
{
    if (!_loginViewModel) {
        _loginViewModel = [LoginViewModel new];
    }
    return _loginViewModel;
}

最后调用bindData方法进行数据绑定
- (void)bindData
{
    RAC(self.loginViewModel, username) = self.rootView.usernameTextField.rac_textSignal;
    RAC(self.loginViewModel, password) = self.rootView.passwordTextField.rac_textSignal;
}

数据绑定测试

如果usernameTextField.text、passwordTextField.text与loginViewModel.username、loginViewModel.password已经绑定数据,那么usernameTextField.text和passwordTextField.text的数据变动的话,一定会引起loginViewModel.username和loginViewModel.password的改变。那么测试用例可以这样设计:



图:数据绑定Test Case

用kiwi编写测试如下:
SPEC_BEGIN(LoginViewControllerSpec)

describe(@"LoginViewController", ^{
    __block LoginViewController *controller = nil;

    beforeEach(^{
        controller = [LoginViewController new];
        [controller view];
    });

    afterEach(^{
        controller = nil;
    });

    describe(@"Root View", ^{
        __block LoginView *rootView = nil;

        beforeEach(^{
            rootView = controller.rootView;
        });

        context(@"when view did load", ^{
            it(@"should bind data", ^{
                rootView.usernameTextField.text = @"samlau";
                rootView.passwordTextField.text = @"freedom";

                [rootView.usernameTextField sendActionsForControlEvents:UIControlEventEditingChanged];
                [rootView.passwordTextField sendActionsForControlEvents:UIControlEventEditingChanged];

                [[controller.loginViewModel.username should] equal:rootView.usernameTextField.text];
                [[controller.loginViewModel.password should] equal:rootView.passwordTextField.text];
            });
        });

    });
});
SPEC_END

这个测试中有两点需要重点解释:
  • 初始化完controller之后,controller一定要调用view方法来加载controller的view,否则不会调用viewDidLoad方法。

引用

如果有些朋友对controller如何管理view生命周期不了解,可以阅读View Controller Programming Guide for iOS文档中的A View Controller Instantiates Its View Hierarchy When Its View is Accessed章节。




图:Loading a view into memory from Apple Document

  • usernameTextField和passwordTextField一定要调用sendActionsForControlEvents方法来通知UI已经更新。

[rootView.usernameTextField sendActionsForControlEvents:UIControlEventEditingChanged];
[rootView.passwordTextField sendActionsForControlEvents:UIControlEventEditingChanged];

一开始时,我并没有调用sendActionsForControlEvents方法导致loginViewModel.username和loginViewModel.password属性并没有更新。当时我开始思考,是不是还需要其他条件还能触发它更新呢?由于我使用UITextField的rac_textSignal属性,于是我就查看它的源代码:
- (RACSignal *)rac_textSignal {
  @weakify(self);
  return [[[[[RACSignal
      defer:^{
          @strongify(self);
          return [RACSignal return:self];
      }]
      concat:[self rac_signalForControlEvents:UIControlEventEditingChanged |  UIControlEventEditingDidBegin]]
      map:^(UITextField *x) {
          return x.text;
      }]
      takeUntil:self.rac_willDeallocSignal]
      setNameWithFormat:@"%@ -rac_textSignal", self.rac_description];
}

从源代码可以知道,只有触发UIControlEventEditingChanged或UIControlEventEditingDidBegin事件时才能创建RACSignal对象。

业务逻辑测试

由于这里需要验证用户名和密码,复用性高,我不将处理逻辑放在viewModel中,而是定义一个DataValidation来处理。这里的用户名是邮箱格式,而密码要求长度大于等于6即可,方法如下:
@interface DataValidation : NSObject

+ (BOOL)isValidEmail:(NSString *)data;
+ (BOOL)isValidPassword:(NSString *)password;

@end

测试用例设计如下:



图:数据验证 Test Case

然后使用kiwi编写测试如下:
SPEC_BEGIN(DataValidationSpec)
describe(@"DataValidation", ^{
    context(@"when email is samlau@163.com", ^{
        it(@"should return YES", ^{
            BOOL result = [DataValidation isValidEmail:@"samlau@163.com"];
            [[theValue(result) should] beYes];
        });
    });
    context(@"when email is samlau163.com", ^{
        it(@"should return YES", ^{
            BOOL result = [DataValidation isValidEmail:@"samlau163.com"];
            [[theValue(result) should] beNo];
        });
    });
    ......省略两个测试用例
});

ViewModel层测试

前面已经完成了数据绑定和数据校验逻辑,接下来思考使用哪个类处理用户名和密码是否有效才能点击和点击按钮后,如何调用网络层在来匹配用户名和密码,RAC提供一个RACCommand类。LoginViewModel定义一个属性loginCommand,并在实现文件中使用Lazy Initialization初始化:
- (RACCommand *)loginCommand
{
    if (!_loginCommand) {
        _loginCommand = [[RACCommand alloc] initWithEnabled:[self isValidUsernameAndPasswordSignal] signalBlock:^RACSignal *(id input) {
            return [LoginClient loginWithUsername:self.username password:self.password];
        }];
    }
    return _loginCommand;
}

上面有一个重要方法isValidUsernameAndPasswordSignal来监听和验证用户名和密码:
- (RACSignal *)isValidUsernameAndPasswordSignal
{
    return [RACSignal combineLatest:@[RACObserve(self, username), RACObserve(self, password)] reduce:^(NSString *username, NSString *password) {
         return @([DataValidation isValidEmail:username] && [DataValidation isValidPassword:password]);
    }];
}

由于上面的方法isValidUsernameAndPasswordSignal已经监听LoginViewModel的username和password,当username和password其中一个改变时,DataValidation类都会调用isValidEmail和isValidPassword来数据验证,并将结果包裹成RACSignal对象返回。

测试用例设计如下:



图:View Model Test Case

然后使用kiwi编写测试如下:
describe(@"LoginViewModel", ^{
    __block LoginViewModel* viewModel = nil;

    beforeEach(^{
        viewModel = [LoginViewModel new];
    });

    afterEach(^{
        viewModel = nil;
    });

    context(@"when username is samlau@163.com and password is freedom", ^{
        __block BOOL result = NO;

        it(@"should return signal that value is YES", ^{
            viewModel.username = @"samlau@163.com";
            viewModel.password = @"freedom";

            [[viewModel isValidUsernameAndPasswordSignal] subscribeNext:^(id x) {
                result = [x boolValue];
            }];

            [[theValue(result) should] beYes];
        });
    });

    ......省略两个测试用例
});

以上测试用例很简单,设置viewModel的username和password,然后调用isValidUsernameAndPasswordSignal返回RACSignal对象,使用subscribeNext获取它的值,最后验证。

网络层测试

最后处理点击登录按钮访问服务器来验证用户名和密码。我定义一个LoginClient类来处理:
@interface LoginClient : NSObject
+ (RACSignal *)loginWithUsername:(NSString *)username password:(NSString *)password;
@end

只要输入username和password两个参数,就能返回是否验证成功的结果被包裹在RACSignal对象中。

由于这里我是使用moco模拟服务,所以只设计一个成功的测试用例:



图:Network Test Case

然后使用kiwi编写测试如下:
describe(@"LoginClient", ^{
    context(@"when username is samlau@163.com and password is samlau", ^{
        __block BOOL success = NO;
        __block NSError *error = nil;

        it(@"should login successfully", ^{
            RACTuple *tuple = [[LoginClient loginWithUsername:@"samlau@163.com" password:@"samlau"] asynchronousFirstOrDefault:nil success:&success error:&error];
            NSDictionary *result = tuple.first;

            [[theValue(success) should] beYes];
            [[error should] beNil];
            [[result[@"result"] should] equal:@"success"];
        });
    });
});

里面使用RAC的一个重要方法asynchronousFirstOrDefault来测试异步网络访问的。详情可参考Test with Reactivecocoa文章。

抓取网络数据并显示情景



如图所示,输入正确的用户名和密码后,跳转到一个食物列表页面,它从服务端抓取图片、价格和已售份数后以列表的方式显示。

网络层测试

首先考虑如何设计和实现API,然后再考虑如何测试。因为它需要从服务端抓取数据,需要设计一个访问食物列表数据的类FoodListClient,设计如下:
@interface FoodListClient : NSObject

+ (RACSignal *)fetchFoodList;

@end

FoodListClient实现如下:
@implementation FoodListClient
+ (RACSignal *)fetchFoodList
{
    return [[[AFHTTPSessionManager manager] rac_GET:[URLHelper URLWithResourcePath:@"/v1/foodlist"] parameters:nil] replayLazily];
}
@end

fetchFoodList方法主要从服务端抓取数据后,返回一个JSON格式的数组。因此想测试这个API,只需要使用RAC的asynchronousFirstOrDefault方法返回RACTuple对象,获取第一个值,测试返回数组不为空即可。使用kiwi编写测试如下:
describe(@"FoodListClient", ^{

    context(@"when fetch food list ", ^{
        __block BOOL successful = NO;
        __block NSError *error = nil;

        it(@"should receive data", ^{
            RACSignal *result = [FoodListClient fetchFoodList];
            RACTuple *tuple = [result asynchronousFirstOrDefault:nil success:&successful error:&error];
            NSArray *foodList = tuple.first;

            [[theValue(successful) should] beYes];
            [[error should] beNil];
            [[foodList shouldNot] beEmpty];
        });
    });
});

Model层测试

抓取完数据后,它的数据格式一般都是JSON格式,需要转化为Model方便访问和修改,通常我都使用Mantle来实现。我定义一个FoodModel类:
@interface FoodModel : MTLModel <MTLJSONSerializing>
/*
 @brief 食物图片URL
 */
@property (copy, nonatomic) NSString *foodImageURL;
/*
 @brief 食物价格
 */
@property (copy, nonatomic) NSString *foodPrice;
/*
 @brief 销量
 */
@property (copy, nonatomic) NSString *saleNumber;
@end

那么如何测试它是否转化成功呢?首先基于上一个网络层测试获取返回JSON格式的食物列表数据,然后调用MTLJSONAdapter类的modelsOfClass: fromJSONArray: error:方法来转化成FoodModel的数组。接下来断言数组不能为空和数组的第一个元素是FoodModel类。

使用kiwi编写测试如下:
describe(@"FoodModel", ^{

    context(@"when JSON data convert to FoodModel", ^{
        __block BOOL successful = NO;
        __block NSError *error = nil;

        it(@"should return FoodModel array", ^{
            // get data from network
            RACSignal *result = [FoodListClient fetchFoodList];
            RACTuple *tuple = [result asynchronousFirstOrDefault:nil success:&successful error:&error];
            NSArray *foodList = tuple.first;

            // assert that foodList can't be empty
            [[theValue(successful) should] beYes];
            [[error should] beNil];
            [[foodList shouldNot] beEmpty];

            // assert that return FoolModel array
            NSArray *foodModelList = [MTLJSONAdapter modelsOfClass:[FoodModel class] fromJSONArray:foodList error:nil];
            [[foodModelList shouldNot] beEmpty];
            [[foodModelList[0] should] beKindOfClass:[FoodModel class]];
        });
    });
});

ViewModel抓取数据

完成抓取网络数据和转化JSON数据为Model后,我使用FoodViewModel来抓取网络数据和完成数据映射,设计与实现如下:
@interface FoodViewModel : RVMViewModel

/*
 @brief FoodModel列表
 */
@property (strong, nonatomic, readonly) NSArray *foodModelList;

@end

@implementation FoodViewModel
- (instancetype)init
{
    self = [super init];

    if (!self) {
        return nil;
    }

    RAC(self, foodModelList) = [[FoodListClient fetchFoodList] map:^id(RACTuple * tuple) {
        return [MTLJSONAdapter modelsOfClass:[FoodModel class] fromJSONArray:tuple.first error:nil];
    }];

    return self;
}
@end

Controller加载数据

最后FoodListViewController负责构建view hierarchy和加载数据:
#pragma mark - Lifecycle
- (void)viewDidLoad
{
    [super viewDidLoad];
    // setup title name and background color
    self.title = @"食物列表";
    self.view.backgroundColor = [UIColor whiteColor];
    // build view hierarchy
    [self buildViewHierarchy];
    // when finish fetching data and reload table view
    [RACObserve(self.foodViewModel, foodModelList) subscribeNext:^(NSArray* items) {
        self.foodListDataSource.items = items;
        [self.tableView reloadData];
    }];
}

总结

编写单元测试是程序员的一项基本技能,如果能够设计好的测试用例并编写测试验证结果,不仅保证代码的质量,而且有利于以后重构加一层保护层。一旦修改了代码之后,如果运行单元测试,并没有通过的话,说明你在重构过程中引入新的Bug。如果通过了单元测试,说明并没有引入新的Bug。

扩展阅读
  • ReactiveCocoa

Test with Reactivecocoa
  • Kiwi

TDD的iOS开发初步以及Kiwi使用入门
Kiwi使用进阶 Mock, Stub, 参数捕获和异步测试

作者简介:

刘耀柱(@Sam_Lau_Dev),iOS Developer兼业余Designer,参与开发技术前线iOS项目翻译,个人博客:http://www.jianshu.com/users/256fb15baf75/latest_articles,GitHub:https://github.com/samlaudev。
  • 大小: 12.3 KB
  • 大小: 40 KB
  • 大小: 6.6 KB
  • 大小: 17.7 KB
  • 大小: 19.4 KB
  • 大小: 24.6 KB
  • 大小: 9.3 KB
  • 大小: 3.1 MB
0
0
评论 共 0 条 请登录后发表评论

发表评论

您还没有登录,请您登录后再发表评论

相关推荐

  • bitmap_indexes bitmap索引的相关知识整理

    Oracle优化调整几乎是Oracle学习中最多,也是最复杂的一项任务。而其中索引的使用又是经常碰到的一个调整优化的难题, 经常听到很多itpub上的同仁讨论使用bitmap index但是又不敢轻易使用它(害怕出现性能问题)。这里参考了Oracle文档,《Beginning Oracle Programming》及《Oracle High-Performance SQL tuning》等书对bitmap index内容作了一些翻译,编辑整理, 希望对大家有些帮助。 有些地方加入了自己的理解,可能有不对的地方,希望各位指正。

  • oracle索引的创建与删除,学习笔记:Oracle索引管理 DBA常用创建、删除、管理各种index索引命令汇总...

    天萃荷净汇总运维DBA日常工作对普通index索引以及唯一index索引的管理命令,创建、更改、删除普通index索引以及唯一index索引案例--查看用户对象SELECT OBJECT_NAME,OBJECT_TYPE FROM USER_OBJECTS;--创建普通索引(指定pctfree,表空间,nologging模式)create index fei_idx3 on fei(pwd,nam...

  • 位图索引Bitmap indexes(数据库索引)

    位图索引是一种使用位图的特殊数据库索引。主要针对大量相同值的列而创建,比如(性别、婚配等字段可选值很少的字段创建位图索引); http://blog.itpub.net/29654823/viewspace-2150299/ https://blog.csdn.net/yidian815/article/details/16891021 ...

  • BitMapIndex,倒排索引原理,B-Tree索引

    Bitmap索引 时序数据库从抽象语义上来说总体可以概括为两个方面的基本需求,一个方面是存储层面的基本需求:包括LSM写入模型保证写入性能、数据分级存储(最近2小时的数据存储在内存中,最近一天的数据存储在SSD中,一天以后的数据存储在HDD中)保证查询性能以及存储成本、数据按时间分区保证时间线查询性能。另一方面是查询层面的基本需求:包括基本的按时间线进行多个维度的原始数据查询、按时间线在多个维度进行聚合后的数据统计查询需求以及TopN需求等。 可见,多维条件查询通常是时序数据库的一个硬需求,其性能好坏也

  • Doris系列9-表结构变更

    文章目录一. 表结构变更概述二. 原理介绍三. 作业3.1 创建作业3.2 查看作业3.3 取消作业四. 最佳实践五. 注意事项六. 常见问题6.1 Schema Change 的执行速度6.2 提交作业报错 Table xxx is not stable. ...七. 相关配置八. 表结构变更实例8.1 新增列8.2 修改列的顺序参考: 一. 表结构变更概述 用户可以通过 Schema Change 操作来修改已存在表的 Schema。目前 Doris 支持以下几种修改: 增加、删除列 修改列类型 调整

  • Bitmap Index vs B-tree Index(原创)

    Introduction Conventional wisdom holds that bitmap indexes are most appropriate for columns having low distinct values--such as GENDER, MARITAL_STATUS, and RELATION. This assumption is not complete...

  • Bitmap Indexes

    位图索引的使用:1.Bitmap indexing benefits data warehousing applications 2.Bitmap indexes are not suitable for OLTP applic...

  • Bitmap Index相关

    Bitmap Index Bitmap index 的特点: 1. 对于大数据量的查询,bitmap index 能更有效的减少响应时间 2. 减少index的占用空间 当查询语句的where 字句中包含多个column时, 位图索引最为有效. 因为在查询表之前, 那些有一个不符合所有column条件的row会直接被bitmap index 过滤掉.这样就大大减少了响应时间. 在多

  • Bitmap index在数据库的使用

    原文

  • b树index和bitmap索引

    ---- create table t05111_gender(a char(1)); ---- begin      for i in 1..50        loop          insert into t05111_gender values('A');          end loop;          commit:          end;

  • 菜单之使用bitmap以及删除bitmap

    菜单之使用bitmap bitmap 加入菜单项 hMenuPopup = LoadMenu(hinst, TEXT("MENUEDIt")); //StretchBitmap 缩小bitmap尺寸 hBitmap = StretchBitmap(LoadBitmap(hinst, TEXT("bitmapedit"))); AppendMenu(hMenu, MF_POPUP | MF...

  • Bitmap Index

    Bitmap IndexBitmap index 的特点:1. 对于大数据量的查询,bitmap index 能更有效的减少响应时间2. 减少index的占用空间当查询语句的where 字句中包含多个column时, 位图索引最为有效. 因为在查询表之前, 那些有一个不符合所有column条件的row会直接被bitmap index 过滤掉.这样就大大减少了响应时间. 在多数情况下, 一般最好是针

  • Oracle Bitmap Index 使用注意点

    Bitmap index: 使用场景是针对那些 值不经常改变的 并且NDV(number of distinct values)较低的字段 如果某个字段频繁更新,例如Flag 字段,是不适合创建bitmap索引的。 应为锁使用机制,位图索引只允许一个用户操作,只有的该会话COMMIT or ROLLBACK 后 第二个会话才能获得锁。 在并发是情况下,这就会限制数据库的性能。 If a ...

  • Oracle数据库中B-Tree以及BitMap index 的性能对比

    索引概述 什么是索引? 索引是Oracle数据库中提供的一种可选的数据结构,用于关联一个表。  为什么要使用索引? 索引在有些情况下可以加快访问速度,减少磁盘IO。 通常情况下时候使用索引? 表中的某列经常会在查询中使用,并且经常用返回占表中数据总量比例较少的row set。引用完整性约束列。unique key 。   下面我们来简

  • bitmap索引的深入研究(自我改版)

    bitmap索引的深入研究 上一篇 / 下一篇  2008-06-10 17:28:21 / 个人分类:工作技术 查看( 952 ) / 评论( 9 ) / 评分( 30 / 1 ) 位图(bitmap)索引是另外一种索引类型,它的组织形式与B树索引相同,也是一棵平衡树。与B树索引的区别在于叶子节点里存放索引条目的方式不同。从前面我们知道,B树索引的叶子节点里,对于

  • 《Oracle SQL优化基础》之位图索引(BitMap index)

    有不少做BI的后台同事问我,位图索引是啥?啥时候用? 此篇仅作为一个扫盲篇,有不对的地方欢迎大神指正。 首先我们要搞明白位图索引是什么,与普通的B*树索引有什么区别呢? 顾名思义,首先他是个索引(废话!),其次是以位图的形式进行存储、计算的。 看个图,下面是我们常用的B*树索引结构(取自网络): 可以看得出,表中索引列的每行数据都会维护到索引树中(Null值除外) 再来看看我们的位图

  • 详解oracle bitmap位图索引

    位图索引是oracle中非常重要的一种索引形式。本文通过总结有关位图索引的资料,尝试回答如下几个问题: 1:什么是位图索引? 2:位图索引适合什么场景,不适合什么场景? 3:位图索引的性能如何? 什么是位图索引? 位图索引,顾名思义,与“位”有关。大家都知道,计算机中的所有信息最终都是通过“位bit”来运算的, 二进制位运算在计算机中是非常高效的。每一个二进制位都可以取值0或者1

  • 位图索引:原理(BitMap index)

    http://www.cnblogs.com/LBSer/p/3322630.html 位图(BitMap)索引   前段时间听同事分享,偶尔讲起Oracle数据库的位图索引,顿时大感兴趣。说来惭愧,在这之前对位图索引一无所知,因此趁此机会写篇博文介绍下位图索引。 1. 案例   有张表名为table的表,由三列组成,分别是姓名、性别和婚姻状况,其中性别只有男和女两项,婚姻状况由已婚、未婚...

  • bitmap index

    bitmap index 适用于 dss(决策支持系统) 和Data warehouse,ORACLE 建议的是不要在繁重的OLTP中使用 bitmap index ,我个人建议:千万别在OLTP中使用bitmap index,否则你死定了。请看一下测试:SQL> create table test as select * from dba_objects;表已创建。SQL> update tes

Global site tag (gtag.js) - Google Analytics