• Grunt


    http://www.cnblogs.com/haogj/p/4813155.html

     Karma 是 Goolge 开源的一个 Test runner, 可以配合 Grunt 使用。

    1. 相关插件介绍

    1.1 Karma 的官网

    http://karma-runner.github.io/

    官网中的文档其实分多钟版本,不同版本的 karma 使用也有所不同,注意页面右上角的版本信息。

    1.2 配合 Grunt 的插件 grunt-karma

    https://github.com/karma-runner/grunt-karma

    1.3 karma 用来启动 chrome 的 karma 插件

    https://github.com/karma-runner/karma-chrome-launcher

    1.4 jasmine 单元测试插件

    https://github.com/karma-runner/karma-jasmine

    1.5 单元测试的报表格式

    单元测试默认提供了两种输出:'dots', 'progress' ,都比较简单,希望看到测试的详细输出,可以使用 reporter 插件,这里是其中之一:mocha

    https://github.com/litixsoft/karma-mocha-reporter

    1.6 angularJS 插件

    https://github.com/karma-runner/karma-ng-scenario

    1.7 requirejs 模块化插件

    https://github.com/karma-runner/karma-requirejs

    2. 安装插件

    2.1 创建 package.json 

    使用 npm init 来创建 package.json 项目文件。

    复制代码
    PS C:studydemo> npm init
    This utility will walk you through creating a package.json file.
    It only covers the most common items, and tries to guess sensible defaults.
    
    See `npm help json` for definitive documentation on these fields
    and exactly what they do.
    
    Use `npm install <pkg> --save` afterwards to install a package and
    save it as a dependency in the package.json file.
    
    Press ^C at any time to quit.
    name: (demo) demo
    version: (1.0.0)
    description:
    entry point: (index.js)
    test command:
    git repository:
    keywords:
    author:
    license: (ISC) MIT
    About to write to C:studydemopackage.json:
    
    {
      "name": "demo",
      "version": "1.0.0",
      "description": "",
      "main": "index.js",
      "scripts": {
        "test": "echo "Error: no test specified" && exit 1"
      },
      "author": "",
      "license": "MIT"
    }
    
    
    Is this ok? (yes) y
    PS C:studydemo>
    复制代码

     安装 Karma

    复制代码
    PS C:studyprotocat> npm install karma --save-dev
    npm WARN package.json app@1.0.0 No description
    npm WARN package.json app@1.0.0 No repository field.
    npm WARN package.json app@1.0.0 No README data
    npm WARN optional dep failed, continuing fsevents@1.0.0
    |
    
    
    > bufferutil@1.2.1 install C:studyprotocat
    ode_moduleskarma
    ode_modulessocket.io
    ode_modulesengine.io
    ode_modulesws
    ode_modulesufferutil
    > node-gyp rebuild
    
    
    C:studyprotocat
    ode_moduleskarma
    ode_modulessocket.io
    ode_modulesengine.io
    ode_modulesws
    ode_modulesufferutil>if not defined npm_config_node_gyp (node "C:Program Files
    odejs
    ode_modules
    pmin
    ode-gyp-bin\....
    ode_modules
    ode-gypin
    ode-gyp.js" rebuild )  else (node  rebuild )
    Building the projects in this solution one at a time. To enable parallel build, please add the "/m" switch.
      bufferutil.cc
    C:Program Files (x86)Microsoft Visual Studio 14.0VCincludelimits(214): warning C4577: 'noexcept' used with no exception handling mode specified; termination on exception is not guaranteed. Specify /EHsc [C:studyprotocat
    ode_moduleskarma
    ode_modulessocket.io
    ode_modulesengine.io
    ode_modulesws
    ode_modulesufferutiluildufferutil.vcxproj]
         Creating library C:studyprotocat
    ode_moduleskarma
    ode_modulessocket.io
    ode_modulesengine.io
    ode_modulesws
    ode_modulesufferutiluildReleaseufferutil.lib and object C:studyprotocat
    ode_moduleskarma
    ode_moduless
      ocket.io
    ode_modulesengine.io
    ode_modulesws
    ode_modulesufferutiluildReleaseufferutil.expGenerating code
      Finished generating code
      bufferutil.vcxproj -> C:studyprotocat
    ode_moduleskarma
    ode_modulessocket.io
    ode_modulesengine.io
    ode_modulesws
    ode_modulesufferutiluildRelease\bufferutil.node
    |
    
    
    > utf-8-validate@1.2.1 install C:studyprotocat
    ode_moduleskarma
    ode_modulessocket.io
    ode_modulesengine.io
    ode_modulesws
    ode_modulesutf-8-validate
    > node-gyp rebuild
    
    
    C:studyprotocat
    ode_moduleskarma
    ode_modulessocket.io
    ode_modulesengine.io
    ode_modulesws
    ode_modulesutf-8-validate>if not defined npm_config_node_gyp (node "C:Program Files
    odejs
    ode_modules
    pmin
    ode-gyp-bin\....
    ode_modules
    ode-gypin
    ode-gyp.js" rebuild )  else (node  rebuild )
    Building the projects in this solution one at a time. To enable parallel build, please add the "/m" switch.
      validation.cc
    C:Program Files (x86)Microsoft Visual Studio 14.0VCincludelimits(214): warning C4577: 'noexcept' used with no exception handling mode specified; termination on exception is not guaranteed. Specify /EHsc [C:studyprotocat
    ode_moduleskarma
    ode_modulessocket.io
    ode_modulesengine.io
    ode_modulesws
    ode_modulesutf-8-validateuildvalidation.vcxproj]
         Creating library C:studyprotocat
    ode_moduleskarma
    ode_modulessocket.io
    ode_modulesengine.io
    ode_modulesws
    ode_modulesutf-8-validateuildReleasevalidation.lib and object C:studyprotocat
    ode_moduleskarma
    ode_modulessocket.io
    ode_modulesengine.io
    ode_modulesws
    ode_modulesutf-8-validateuildReleasevalidation.expGenerating code
      Finished generating code
      validation.vcxproj -> C:studyprotocat
    ode_moduleskarma
    ode_modulessocket.io
    ode_modulesengine.io
    ode_modulesws
    ode_modulesutf-8-validateuildRelease\validation.node
    /
    
    
    > utf-8-validate@1.2.1 install C:studyprotocat
    ode_moduleskarma
    ode_modulessocket.io
    ode_modulessocket.io-client
    ode_modulesengine.io-client
    ode_modulesws
    ode_modulesutf-8-validate
    > node-gyp rebuild
    
    
    C:studyprotocat
    ode_moduleskarma
    ode_modulessocket.io
    ode_modulessocket.io-client
    ode_modulesengine.io-client
    ode_modulesws
    ode_modulesutf-8-validate>if not defined npm_config_node_gyp (node "C:Program Files
    odejs
    ode_modules
    pmin
    ode-gyp-bin\....
    ode_modules
    ode-gypin
    ode-gyp.js" rebuild )  else (node  rebuild )
    Building the projects in this solution one at a time. To enable parallel build, please add the "/m" switch.
      validation.cc
    C:Program Files (x86)Microsoft Visual Studio 14.0VCincludelimits(214): warning C4577: 'noexcept' used with no exception handling mode specified; termination on exception is not guaranteed. Specify /EHsc [C:studyprotocat
    ode_moduleskarma
    ode_modulessocket.io
    ode_modulessocket.io-client
    ode_modulesengine.io-client
    ode_modulesws
    ode_modulesutf-8-validateuildvalidation.vcxproj]
         Creating library C:studyprotocat
    ode_moduleskarma
    ode_modulessocket.io
    ode_modulessocket.io-client
    ode_modulesengine.io-client
    ode_modulesws
    ode_modulesutf-8-validateuildReleasevalidation.lib and object C:studyprotocat
    ode_moduleskarma
    ode_modulessocket.io
    ode_modulessocket.io-client
    ode_modulesengine.io-client
    ode_modulesws
    ode_modulesutf-8-validateuildReleasevalidation.expGenerating code
      Finished generating code
      validation.vcxproj -> C:studyprotocat
    ode_moduleskarma
    ode_modulessocket.io
    ode_modulessocket.io-client
    ode_modulesengine.io-client
    ode_modulesws
    ode_modulesutf-8-validateuildRelease\validation.node
    |
    
    
    > bufferutil@1.2.1 install C:studyprotocat
    ode_moduleskarma
    ode_modulessocket.io
    ode_modulessocket.io-client
    ode_modulesengine.io-client
    ode_modulesws
    ode_modulesufferutil
    > node-gyp rebuild
    
    /
    C:studyprotocat
    ode_moduleskarma
    ode_modulessocket.io
    ode_modulessocket.io-client
    ode_modulesengine.io-client
    ode_modulesws
    ode_modulesufferutil>if not defined npm_config_node_gyp (node "C:Program Files
    odejs
    ode_modules
    pmin
    ode-gyp-bin\....
    ode_modules
    ode-gypin
    ode-gyp.js" rebuild )  else (node  rebuild )
    Building the projects in this solution one at a time. To enable parallel build, please add the "/m" switch.
      bufferutil.cc
    C:Program Files (x86)Microsoft Visual Studio 14.0VCincludelimits(214): warning C4577: 'noexcept' used with no exception handling mode specified; termination on exception is not guaranteed. Specify /EHsc [C:studyprotocat
    ode_moduleskarma
    ode_modulessocket.io
    ode_modulessocket.io-client
    ode_modulesengine.io-client
    ode_modulesws
    ode_modulesufferutiluildufferutil.vcxproj]
         Creating library C:studyprotocat
    ode_moduleskarma
    ode_modulessocket.io
    ode_modulessocket.io-client
    ode_modulesengine.io-client
    ode_modulesws
    ode_modulesufferutiluildReleaseufferutil.lib and object C:studyprotocat
    ode_moduleskarma
    ode_modulessocket.io
    ode_modulessocket.io-client
    ode_modulesengine.io-client
    ode_modulesws
    ode_modulesufferutiluildReleaseufferutil.exp
      Generating code
      Finished generating code
      bufferutil.vcxproj -> C:studyprotocat
    ode_moduleskarma
    ode_modulessocket.io
    ode_modulessocket.io-client
    ode_modulesengine.io-client
    ode_modulesws
    ode_modulesufferutiluildRelease\bufferutil.node
    karma@0.13.10 node_moduleskarma
    ├── di@0.0.1
    ├── rimraf@2.4.3
    ├── graceful-fs@4.1.2
    ├── mime@1.3.4
    ├── colors@1.1.2
    ├── bluebird@2.10.1
    ├── http-proxy@1.11.2 (eventemitter3@1.1.1, requires-port@0.0.1)
    ├── source-map@0.4.4 (amdefine@1.0.0)
    ├── useragent@2.1.7 (lru-cache@2.2.4)
    ├── optimist@0.6.1 (wordwrap@0.0.3, minimist@0.0.10)
    ├── dom-serialize@2.2.0 (void-elements@1.0.0, custom-event@1.0.0, ent@2.2.0, extend@2.0.1)
    ├── minimatch@2.0.10 (brace-expansion@1.1.0)
    ├── glob@5.0.14 (path-is-absolute@1.0.0, inherits@2.0.1, inflight@1.0.4, once@1.3.2)
    ├── lodash@3.10.1
    ├── expand-braces@0.1.1 (array-uniq@1.0.2, array-slice@0.2.3, braces@0.1.5)
    ├── connect@3.4.0 (utils-merge@1.0.0, parseurl@1.3.0, finalhandler@0.4.0, debug@2.2.0)
    ├── log4js@0.6.27 (async@0.2.10, underscore@1.8.2, semver@4.3.6, readable-stream@1.0.33)
    ├── core-js@1.1.4
    ├── memoizee@0.3.9 (next-tick@0.2.2, lru-queue@0.1.0, timers-ext@0.1.0, d@0.1.1, event-emitter@0.3.3, es6-weak-map@0.1.4
    , es5-ext@0.10.7)
    ├── body-parser@1.14.0 (bytes@2.1.0, content-type@1.0.1, depd@1.1.0, on-finished@2.3.0, raw-body@2.1.3, debug@2.2.0, ico
    nv-lite@0.4.11, qs@5.1.0, http-errors@1.3.1, type-is@1.6.8)
    ├── chokidar@1.1.0 (arrify@1.0.0, path-is-absolute@1.0.0, is-glob@2.0.0, async-each@0.1.6, glob-parent@2.0.0, is-binary-
    path@1.0.1, readdirp@2.0.0, anymatch@1.3.0)
    └── socket.io@1.3.7 (has-binary-data@0.1.3, debug@2.1.0, socket.io-parser@2.2.4, socket.io-adapter@0.3.1, engine.io@1.5.
    4, socket.io-client@1.3.7)
    复制代码

    安装 grunt-karma 插件。

    PS C:studydemo> npm install grunt-karma --save-dev
    npm WARN package.json app@1.0.0 No description
    npm WARN package.json app@1.0.0 No repository field.
    npm WARN package.json app@1.0.0 No README data
    grunt-karma@0.12.1 node_modulesgrunt-karma
    └── lodash@3.10.1

    安装之后,在 node_modules 中实际上有了 grunt, grunt-karma, 以及 karma 三个组件。

    继续安装 karma 的插件。

    先安装 chrome 的启动器。

    复制代码
    PS C:studydemo> npm install --save-dev karma-chrome-launcher
    npm WARN package.json demo@1.0.0 No description
    npm WARN package.json demo@1.0.0 No repository field.
    npm WARN package.json demo@1.0.0 No README data
    karma-chrome-launcher@0.2.0 node_moduleskarma-chrome-launcher
    ├── fs-access@1.0.0 (null-check@1.0.0)
    └── which@1.1.2 (is-absolute@0.1.7)
    复制代码

    单元测试 jasmine.

    复制代码
    PS C:studydemo> npm install --save-dev karma-jasmine
    npm WARN package.json demo@1.0.0 No description
    npm WARN package.json demo@1.0.0 No repository field.
    npm WARN package.json demo@1.0.0 No README data
    npm WARN peerDependencies The peer dependency jasmine-core@* included from karma-jasmine will no
    npm WARN peerDependencies longer be automatically installed to fulfill the peerDependency
    npm WARN peerDependencies in npm 3+. Your application will need to depend on it explicitly.
    jasmine-core@2.3.4 node_modulesjasmine-core
    
    karma-jasmine@0.3.6 node_moduleskarma-jasmine
    复制代码

    单元测试的输出报表格式 mocha.

    PS C:studydemo> npm install --save-dev karma-mocha-reporter
    npm WARN package.json demo@1.0.0 No description
    npm WARN package.json demo@1.0.0 No repository field.
    npm WARN package.json demo@1.0.0 No README data
    karma-mocha-reporter@1.1.1 node_moduleskarma-mocha-reporter
    └── chalk@1.1.0 (escape-string-regexp@1.0.3, supports-color@2.0.0, ansi-styles@2.1.0, has-ansi@2.0.0, strip-ansi@3.0.0)

    angular 插件

    PS C:studydemo> npm install --save-dev karma-ng-scenario
    npm WARN package.json demo@1.0.0 No description
    npm WARN package.json demo@1.0.0 No repository field.
    npm WARN package.json demo@1.0.0 No README data
    karma-ng-scenario@0.1.0 node_moduleskarma-ng-scenario

    requirejs 插件。

    复制代码
    PS C:studydemo> npm install --save-dev karma-requirejs
    npm WARN package.json demo@1.0.0 No description
    npm WARN package.json demo@1.0.0 No repository field.
    npm WARN package.json demo@1.0.0 No README data
    npm WARN peerDependencies The peer dependency requirejs@~2.1 included from karma-requirejs will no
    npm WARN peerDependencies longer be automatically installed to fulfill the peerDependency
    npm WARN peerDependencies in npm 3+. Your application will need to depend on it explicitly.
    requirejs@2.1.20 node_modules
    equirejs
    
    karma-requirejs@0.2.2 node_moduleskarma-requirejs
    复制代码

    3. 配置

     不用版本的 karma 配置会有所不同,这里使用 v0.13 版。

     karma 的运行配置可以保存在独立的文件中,也可以在 Gruntfile.js 中直接配置,由于 karma 的配置参数较多,建议保存在独立的配置文件中。

    这样,我们可以针对不同的测试场景,创建不同的 karma 配置文件,最后通过 grunt 来使用。

    karma 配置文件中的 basePath 是一个非常重要的参数,用来表示 karma 配置文件中的其它相对路径所依赖的起点。

    如果这是一个对象路径,就会相对于配置文件来定位。

    默认是 '', 也就是说配置文件所在的目录就是相对的根目录了。

    在 karma 实际运行的时候,这个目录会被自动映射到 /base 之下。

    files 中配置可以通过 karma 服务器访问哪些文件,在这里列出的文件,才可以通过 HTTP 访问到。当使用对象方式表示路径的时候,included 表示 karma 是不是需要自动生成一个 script 标记来加载特定的脚本,设置为 false 表示不生成这个标记,而 served 则表示是否可以通过 karma 的网站服务器访问这个文件。

    plugins 在 v0.13 中可以不同配置,karma 会自动加载位于 node_modules 中的 karma-* 的 karma 模块。

    复制代码
    // Karma configuration
    // Generated on Tue Sep 15 2015 12:41:55 GMT+0800 (China Standard Time)
    
    module.exports = function(config) {
      config.set({
    
        // base path that will be used to resolve all patterns (eg. files, exclude)
        basePath: '',
    
    
        // frameworks to use
        // available frameworks: https://npmjs.org/browse/keyword/karma-adapter
        // if requirejs used, 
        frameworks: ['jasmine', 'requirejs'],
    
    
        // list of files / patterns to load in the browser
        files: [
        
          // inclueded, default: true, should the files be included in the browser using <script> tag?
          // use false if you want to load them manually. eg using require.js
          // served, default: true, should the files be served by karma's webserver?
          // watched, default: true, if autowatch is true, all files that have set watched to true will be watched for change
          { pattern: 'src/**/*.*', included: false, served: true },
          { pattern: 'test/**/*_spec.js', included: false, served: true },
    
          //in version 0.13, the item should be last
          'test-main.js',
        ],
    
    
        // list of files to exclude
        exclude: [
            'src/js/main.js'
        ],
    
    
        // preprocess matching files before serving them to the browser
        // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
        preprocessors: {
        },
    
    
        // test results reporter to use
        // possible values: 'dots', 'progress'
        // available reporters: https://npmjs.org/browse/keyword/karma-reporter
        reporters: ['mocha'],
    
    
        // web server port
        port: 9876,
    
    
        // enable / disable colors in the output (reporters and logs)
        colors: true,
    
    
        // level of logging
        // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
        logLevel: config.LOG_INFO,
    
    
        // enable / disable watching file and executing tests whenever any file changes
        autoWatch: true,
    
    
        // start these browsers
        // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
        browsers: ['Chrome'],
    
    
        // Continuous Integration mode
        // if true, Karma captures browsers, runs the tests and exits
        singleRun: false,
    
        // By default, Karma loads all sibling NPM modules which have a name starting with karma-*.
      })
    }
    复制代码

    在配合 requirejs 的时候,我们还需要一个 main.js 文件对 requirejs 进行配置。

    在 requirejs 中也有一个 baseUrl 配置参数,这里一定要考虑刚才karma 的 basePath 进行配置,由于在 karma 的网站中,前面又添加了 /base ,所以,现在 requirejs 的 baseUrl 就会变成 /base/src/js 了。

    这样,修改了 baseUrl, 我们其它的模块 Id 就不用变化了。

    我们希望能够通过 karma 直接加载测试并执行测试,所以,karma 帮助我们生成了一段脚本,从我们网站中的脚本文件中筛选出测试文件,直接加载执行,脚本通过正则表达式  /(spec|test).js$/i 来筛选,可以看到,它直接检查脚本文件名中,以 spec.js 或者 test.js 结尾的脚本文件。找到之后,通过 requirejs 配置中的 deps 提前加载并执行。

    复制代码
    var allTestFiles = [];
    var TEST_REGEXP = /(spec|test).js$/i;
    
    // Get a list of all the test files to include
    Object.keys(window.__karma__.files).forEach(function(file) {
      if (TEST_REGEXP.test(file)) {
        // Normalize paths to RequireJS module names.
        // If you require sub-dependencies of test files to be loaded as-is (requiring file extension)
        // then do not normalize the paths
        
        // var normalizedTestModule = file.replace(/^/base/|.js$/g, '');
        var normalizedTestModule = file;
        allTestFiles.push(normalizedTestModule);
      }
    });
    
    require.config({
      // Karma serves files under /base, which is the basePath from your config file
      baseUrl: '/base/src/js',
        paths:{
          'angular':'lib/angular.min'
        },
      shim:{
        'angular':{
          'exports': 'angular'
        }
      },
    
      // dynamically load all test files
      deps: allTestFiles,
    
      // we have to kickoff jasmine, as it is asynchronous
      callback: window.__karma__.start
    });
    
    require(['angular', 'controllers/controllers'], function(angular, controller){
      console.log('main.js loaded.');
      console.log( controller);
      
    }, function(){
      console.log("load error");
      console.log( arguments);
    });
    复制代码

    4. 测试的写法

    比如,我们有一个模块的定义。

    复制代码
    define([], function(){
        
        return function ($scope){
    
            $scope.name = 'World';
    
            $scope.phones = [
                {'name': 'Nexus S', 'snippet': 'Fast just got faster with Nexus S.', 'age':1 },
                {'name': 'Motorola XOOM™ with Wi-Fi', 'snippet': 'The Next, Next Generation tablet.', 'age':2 },
                {'name': 'MOTOROLA XOOM™', 'snippet': 'The Next, Next Generation tablet.', 'age':3 }
              ];
    
              $scope.orderProp = 'age';
        };
    })
    复制代码

    测试也是一个模块,一定要在测试的依赖项中,将测试所依赖的模块加载进来。

    复制代码
    // use define to load dependency block.
    define(['controllers/controllers'], function(target) {
        
        // regular jasmine test case
        describe('PhoneCat controllers', function() {
            describe('PhoneListCtrl', function(){
    
                var scope, ctrl;
    
                beforeEach(function(){
                    scope = {};
    
                    // create controller instance
                    ctrl = new target( scope );
                });
    
                it('should create "Phone" model with 3 phones.', function(){
                    expect( scope.phones.length ).toBe( 3 );
                });
    
                it(' the name of "Phone" model should be "World".', function(){
                    expect( scope.name ).toBe('World');
                });
    
                it('the age of "Phone" model should be "age",', function(){
                    expect( scope.orderProp ).toBe( 'age' );
                })
            })
        });
    });
    复制代码

     执行之后的输出如下:

    复制代码
    PS C:studyphotocat> grunt karma
    Running "karma:unit" (karma) task
    16 09 2015 14:38:05.540:WARN [karma]: No captured browser, open http://localhost:9876/
    16 09 2015 14:38:05.555:INFO [karma]: Karma v0.13.9 server started at http://localhost:9876/
    16 09 2015 14:38:05.564:INFO [launcher]: Starting browser Chrome
    16 09 2015 14:38:08.382:INFO [Chrome 44.0.2403 (Windows 7 0.0.0)]: Connected on socket IUSWkKhxqSbjN8NzAAAA with id 89877642
    
    Start:
      hello, jasmine.
        √ say hello to jasmine.
      PhoneCat controllers
        PhoneListCtrl
          √ should create "Phone" model with 3 phones.
          √  the name of "Phone" model should be "World".
          √ the age of "Phone" model should be "age",
    
    Finished in 0.013 secs / 0.002 secs
    
    SUMMARY:
    √ 4 tests completed
    复制代码

    5. 总结

    在配合 requirejs 的时候, karma 路径,requirejs 的路径一定要注意,karma 的服务器将我们的服务内容放在了 /base 之下,我们在使用的时候,必须特别注意这一点。

    另外,文件名称的大小写问题,在 Windows 系统下大小写不敏感,但是,在 Linux 服务器上,大小写不同将是不同的文件。注意 Karma 服务器中,即使在 Windwos 系统下,也会区分大小写。

    在配合 requirejs 时,关键是一定要通过 requirejs 来加载你的模块,而不是直接嵌入带页面中。 

    http://requirejs.org/docs/errors.html#mismatch

    http://icodeit.org/2013/10/using-karma-as-the-javascript-test-runner/ 

  • 相关阅读:
    P3图片导致iOS9.3以下崩溃问题
    [ios] 如何调用其他app h5界面调用打开app
    Swift学习中遇到的小坑
    代码行数统计(mac)
    路径专题 绝对路径 根路径 相对路径
    java.lang.StackOverflowError 解决办法
    Myeclipse运行提示错误: 找不到或无法加载主类 test.test1 终极解决办法
    myeclipse的最有用的设置
    关闭myeclipse可视化视图
    数据三大范式
  • 原文地址:https://www.cnblogs.com/koleyang/p/5570547.html
Copyright © 2020-2023  润新知