主頁 >  其他 > 使用Sinon和Rewire對JavaScript中的私有方法進行單元測驗

使用Sinon和Rewire對JavaScript中的私有方法進行單元測驗

2021-12-07 15:23:28 其他

  我們曾經試圖遵循良好的編程習慣,在創建和定義方法時盡可能按照“職責單一”和“開放-封閉”原則將那些沒有必要暴露出來的方法定義為私有方法,但是在撰寫測驗用例時又往往對這些設計原則嗤之以鼻,因為你會為無法撰寫測驗這些私有方法的測驗用例而感到苦惱,

  從互聯網上找到的許多方法都不是最優解決方案,在本文中,我會首先介紹其中的一些解決方案,并指出它們存在的一些問題,然后,我會提供一些生產級別的代碼,它們包含了能夠通過單元測驗并成功實作100%覆寫率的一些私有方法,這些示例都是Node.js的代碼,

  首先,讓我們考察下面的代碼:

module.exports = {
    myPubFunc: async function() {
        let val = await myPrivFunc();

        // do something (perhaps, call 'myPrivFunc' again, if necessary);

        return true;
    }
}

function myPrivFunc() {
    return new Promise((resolve, reject) => {
        // if something is successful or true, 'resolve', otherwise, 'reject'
    });
}

  這里我們暫時忽略函式myPrivFunc的具體內容,因為這不是我們關心的重點,這里主要的問題是函式myPrivFunc有其自身的代碼邏輯,它實作了單一的功能并且可能在該模塊內部被多次使用——僅限于在模塊內部被呼叫,正如你所看到的,創建私有函式增加了代碼的可讀性和可管理性,同時減少了代碼的復雜性和重復代碼的出現,有關是否需要私有方法的爭論有很多,但至今還沒有人能給出一個充分的理由,

反私有方法模式

  針對私有方法,有許多不太好的解決方案,這些解決方案大多都來自互聯網上的博客,在介紹如何正確地對私有函式進行單元測驗之前,我想先說明一下哪些應該做哪些不應該做,在你使用這些解決方案之前,你應該首先搞清楚為什么要將函式設定為私有的,使用訪問修飾符(如private,internal,protected等)而不使用public的原因有很多——即使修飾符本身在某些語言(如JavaScript)中可能沒有被顯式地使用,但是在代碼中也可以被隱式地實作,

  結合上面給出的示例代碼,我將給出一些反私有方法的例子,并說明這些解決方法為什么不好,

沒有私有函式

  誠然,有些人為了單元測驗撰寫方便將所有的函式都公開,我不太贊同這一點,如果你遵循設計原則使得每個函式都具有單一的功能,那么一個良好的、可控的集成測驗是足以覆寫到所有的私有方法的,

“隱藏”屬性

  我們將上面的示例代碼改寫如下:

module.exports = {
    myPubFunc: async function() {
        let val = await myPrivFunc();

        // do something (perhaps, call 'myPrivFunc' again, if necessary);

        return true;
    },

    __private__ : {
        myPrivFunc: myPrivFunc
    }
}

  這樣一來,私有函式myPrivFunc將通過“私有”屬性在該模塊中被暴露出來,我們通過一個名為"__private__"的公共屬性將私有屬性設定為公共屬性,從而去掉了私有函式,然后,該屬性并非真正的私有屬性,因為編輯器的智能感知功能仍然會顯式該屬性(如下圖所示),

   所以,這其實并沒有任何意義,因為你已經暴露了私有方法,這和你最初的設計背道而馳,

模塊依賴

  我們將上面的示例代碼改寫如下:

var private = require('./private');

module.exports = {
    myPubFunc: async function() {
        let val = await private.myPrivFunc();

        // do something (perhaps, call 'myPrivFunc' again, if necessary);

        return true;
    }
}

  同樣,你在這個模塊中去掉了私有函式,但是該函式卻成了另一個模塊中的公共函式,并且,你也不能阻止其他用戶在無意中直接呼叫你設定的這個所謂的私有函式,這種設計在生產環境中通常是危險的(因為,將函式設定為私有的總是有原因的),另外,這里的命名也會讓人覺得很詭異(如private.myPrivateFunc),

過載測驗

  這可以是幾種反私有方法模式的混合,考慮下面的代碼:

module.exports = {
    myPubFunc: async function() {
        let val = await myPrivFunc();

        // do something (perhaps, call 'myPrivFunc' again, if necessary);

        return true;
    }

  /* test-code */
  ,testPrivFunc: function(fn) {
        myPrivFunc = fn;
    }
   /* end-test-code */
}

   這個糟糕的設計提供了一個特殊的方法,用來將一個函式作為引數賦值給myPrivFunc,但是testPrivFunc只用于進行單元測驗,在生產環境中將會被忽略,因為它包含在特定的注釋塊中,開發人員會使用諸如gulp工具以及像gulp-strip-code這樣的插件來清楚這些特定注釋塊之間的代碼,以便在構建生產代碼時保持代碼的整潔,保持生產級別的代碼干凈是件好事,但為什么開發環境的代碼就一定要是臟的呢?為什么我們不能使開發環境和生產環境的代碼都是干凈的呢?這樣我們在除錯時也會方便些,

依賴注入

  到目前為止,這個是最復雜的反私有函式模式,不過這僅僅只是為了在進行單元測驗時保證私有函式的安全,而且是多此一舉,查看下面的代碼(非ES6):

function PublicFuncs(privateService) { 
    this.privateService = privateService;
}

PublicFuncs.prototype.myPubFunc = async function() {
    let val = await this.privateService.myPrivFunc();

    // do something (perhaps, call 'myPrivFunc' again, if necessary);

    return true;
}

module.exports = {
    PublicFuncs: PublicFuncs
}

  注意,這里的myPrivFunc是一個服務提供者的屬性,它在實體化時通過某種依賴注入的方式來提供,如上面的代碼在實際使用場景中會像下面這樣:

var publicFuncs = require('./publicFuncs').PublicFuncs(privateService);

var result = publicFuncs.myPubFunc();

  從純技術的角度來說這是可行的,但這完全是多此一舉,依賴注入被用來在服務和消費者之間實作松耦合是一個不錯的設計,而在同一個類或模塊中的公共方法和私有方法之間通過依賴注入實作松耦合卻有點大材小用,

  以上是幾種反私有方法模式的介紹,接下來我將介紹如何對私有函式進行單元測驗,

 

  為了說明如何正確地對私有函式進行單元測驗,我將使用下面的代碼來嘗試洗掉一個臨時的markdown檔案,

  注意:模塊messaging只是一個簡單的模塊,它的作用是將標準訊息寫入控制臺,為了提高可維護性,我將所有面向用戶的訊息都存盤在一個檔案中,對這個示例而言,訊息本身并不重要,在我們的單元測驗中它將會被stub掉,

var fs = require('fs');
var path = require('path');
var messaging = require('./messaging');


module.exports = {
    /**
     * Deletes the `temppdf.md` file.
     * 
     * Deletes the temporary PDF markdown file.
     * 
     * @returns {boolean}       Returns true if the file is was successfully deleted (or non-existent). Returns false if the file cannot be deleted (e.g. file lock, etc.).
     */
    deleteTempPdf: async function () {

        let tempPdf = path.resolve('temppdf.md');
        let stat = await checkFileAccess(tempPdf);

        if (stat == 0) {
            messaging.printTempPdfMessage(0);
            return true;
        } else if (stat == 1) {
            messaging.printTempPdfMessage(1);
            return false;
        } else if (stat == 2) {
            // File is writable (e.g. no lock), attempt to delete
            fs.unlinkSync(tempPdf);

            // Check file status again
            stat = await checkFileAccess(tempPdf);

            if (stat == 0) {
                messaging.printTempPdfMessage(2);
                return true;
            } else {
                messaging.printTempPdfMessage(3);
                return false;
            }
        } else {
            messaging.printTempPdfMessage(4);
            return false;
        }
    }
}

/**
 * Checks file access.
 * 
 * Checks a given file for access on the filesystem.
 * 
 * @param {string} file         Path of the file.
 * 
 * @return {number}             0 if file doesn't exist; 1 if file is readonly; 2 if file is writable
 */
 function checkFileAccess(file) {
    return new Promise((resolve) => {
        fs.access(file, fs.constants.F_OK | fs.constants.W_OK, (err) => {
            if (err) {
                if (err.code === 'ENOENT') {
                    resolve(0);
                } else {
                    resolve(1);
                }
            } else {
                resolve(2);
            }
        });
    });
}

  這里你看到有兩個方法,deleteTempPdf是一個公共方法,checkFileAccess是一個私有方法,checkFileAccess使用fs庫來檢查檔案的訪問條件,

  注意:fs.exists方法已被棄用,因此這里我們使用fs.access

  如你所見,我們從JSDoc的描述中得知,'checkFileAccess'回傳三種可能的值:檔案不存在回傳'0';檔案只讀回傳'1'(如檔案被鎖定,或者當前權限不允許寫檔案);檔案存在并可寫回傳'2',

  基于這些回傳值,函式deleteTempPdf將會進行相應的操作,如果檔案存在并洗掉成功,或者檔案不存在,deleteTempPdf將回傳true,否則,deleteTempPdf將回傳false,表示檔案不能洗掉,

  查看deleteTempPdf函式中的if-then結構,邏輯如下:

  1. 如果檔案不存在,回傳true
  2. 否則,如果檔案是只讀的,回傳false
  3. 否則,如果檔案存在并且是可寫的:
    1. 嘗試洗掉檔案,然后再次檢查檔案
    2. 如果檔案不存在(洗掉成功),回傳true
    3. 否則(某些原因檔案沒有洗掉成功),回傳false
  4. 否則(未知問題),回傳false

  根據以上幾個分支,我們可以確定需要撰寫下面幾個單元測驗:

  1. it('should return true for "temppdf.md" not existing')
  2. it('should return false for "temppdf,md" being readonly')
  3. it('should return true for "temppdf.md" existing, being writable and being deletred successfully')
  4. it('should return false for "temppdf.md" existing, being writable, but not deleted successfully')
  5. it('should return false for unknown error when checking access of "temppdf.md"')

  下面是我們單元測驗檔案的第一個版本:

describe('tempFile', () => {
    describe('deleteTempPdf', () => {
        it('should return true for "temppdf.md" not existing', () => {

        })

        it('should return false for "temppdf.md" being readonly', () => {

        })

        it('should return true for "temppdf.md" existing, being writable and being deleted successfully', () => {

        })

        it('should return false for "temppdf.md" existing, being writable, but not deleted successfully', () => {

        })

        it('should return false for unknown error when checking access of "temppdf.md"', () => {

        })
    })
})

  成功撰寫完這幾個單元測驗,我們的代碼率將達到100%,

  接下來我們將實作這些單元測驗的具體代碼,

 

先決條件

  我們將使用MochaChai對單元測驗進行斷言,所以,首先需要將它們添加到專案中:

npm i mocha chai --save-dev

  另外,由于我們的私有方法checkFileAccess使用了promise,Chai有一個額外的庫可以支持promise,我們將其一并添加到專案中:

npm i chai-as-promised --save-dev

  最后,我們在測驗檔案的頭部添加以下代碼來匯入這些庫,以便在我們的單元測驗中使用它們:

var chai = require('chai');
var chaiAsPromised = require('chai-as-promised');
chai.use(chaiAsPromised).should();

額外說明

  有許多Node modules可以"mock"檔案系統,以便對使用fs的方法進行單元測驗,它們實際上會創建一個物理存在的、臨時的檔案結構,但是在我看來,這并非是一個理想的測驗環境,因為1)從技術上來講,這是一個集成測驗;2)你的test runner有可能會并行執行測驗,并在檔案存在或者不應該存在的地方產生一些問題;其次3)如果你的test runner在測驗程序中出錯,檔案系統不會被清理,在測驗程序中產生的檔案需要在下次測驗之前手動清理(例如洗掉臨時檔案和檔案夾),基于這些原因,我更加傾向于對fs進行stub,并控制輸出結果,

  為了對fs進行stub,我們需要對fs.access回傳的錯誤代碼進行stub,讓我們把這個變數添加到describe陳述句的前面:

var err;

前三個測驗

  前三個測驗非常簡單,你可以在下面的代碼中看到,但是我們的測驗實際上還沒有通過,因為我們還沒有對fsprintTempPdsMessage方法進行stub,后面馬上就會講到,我們繼續在測驗用例中添加必要的代碼,

  我們將分別介紹每個測驗用例,

        it('should return true for "temppdf.md" not existing', () => {
            err = { 
                code: 'ENOENT'
            };

            return tempFile.deleteTempPdf().should.eventually.be.true;
        })

   在第一個測驗用例中,fs.access應該回傳一個object,其中的code值ENOENT表示檔案temppdf.md不存在,所以,我們mock該object以確保fs.access回傳正確的結果,這樣的話,checkFileAccess將回傳'0'從而滿足我們的第一個條件,測驗代碼中的should.eventually.be斷言是chai-as-promised提供的Chai的擴展,允許我們可以測驗checkFileAccess方法的promise回傳值,最后需要注意的是,tempFile是通過模塊匯入到測驗檔案中的,我們會在后面匯入該檔案,

        it('should return false for "temppdf.md" being readonly', () => {
            err = {
                code: 'SOMETHING_ELSE'
            };

            return tempFile.deleteTempPdf().should.eventually.be.false;
        })

  這個測驗和第一個測驗很相似,只是error code不同,在第一個測驗中,我們通過ENOENT來表示temppdf.md檔案不存在,但是在這個測驗中,我們希望檔案存在,但是是只讀的,為了進行測驗,我們只需要error code不是ENOENT就可以,所以這里我們隨便提供了一個code,

        it('should return false for "temppdf.md" existing, being writable, but not deleted successfully', () => {
            err = null;

            return tempFile.deleteTempPdf().should.eventually.be.false;
        })

   第三個測驗的err是null,這將促使checkFileAccess回傳'2',并且在deleteTempPdf方法中兩次呼叫checkFileAccess并最侄訓傳false,

  至此,我們已經完成了三個測驗,但是它們還不能運行,因為我們還需要對一些方法進行stub,接下來就是見證奇跡的時刻了,到目前為止,你的測驗代碼應該像下面這樣:

describe('tempFile', () => {
    var err;

    describe('deleteTempPdf', () => {
        it('should return true for "temppdf.md" not existing', () => {
            err = { 
                code: 'ENOENT'
            };

            return tempFile.deleteTempPdf().should.eventually.be.true;
        })

        it('should return false for "temppdf.md" being readonly', () => {
            err = {
                code: 'SOMETHING_ELSE'
            };

            return tempFile.deleteTempPdf().should.eventually.be.false;
        })

        it('should return true for "temppdf.md" existing, being writable and being deleted successfully', () => {

        })

        it('should return false for "temppdf.md" existing, being writable, but not deleted successfully', () => {
            err = null;

            return tempFile.deleteTempPdf().should.eventually.be.false;
        })

        it('should return false for unknown error when checking access of "temppdf.md"', () => {

        })
    })
})

 

  好了,接下來讓我們進入到最激動人心的部分——在測驗用例中處理私有方法,

Sinon + Rewire

  為了實作這一功能,我們需要引入兩個得力助手——SinonRewire,稍后我會介紹它們的用途,首先讓我們將它們添加到專案中:

npm i sinon rewire --save-dev

  當然,我們還需要在測驗檔案中添加對它們的參考:

var sinon = require('sinon');
var rewire = require('rewire');

  Sinon是一個非常強大的庫,用于輔助進行單元測驗,它允許我們對單元測驗撰寫stubs、shims和mocks,有了Sinon,我們可以對stubs和shims進行控制,以驗證它們是否被呼叫了以及被呼叫了多少次,Sinon有非常多的功能,我無法在這里一一列出,有關更詳細的介紹可以查看它的官網,

  Rewire提供了一個由Node.js撰寫的被稱之為模塊封裝的功能,Rewire可以重新封裝我們的模塊,從而允許我們"rewire"快取的版本并從記憶體中替換掉原有模塊中的屬性,這樣,我們就可以在記憶體中重寫checkFileAccess函式,而不必采用我們前面提到的反私有方法模式,我們可以反復呼叫rewire,而rewire每次都會創建一個新的快取版本,

Setup

  除了我們前面添加的err變數外,我們還需要另外兩個變數——一個是用于測驗的sandbox(用于stubs),另一個用來快取我們的測驗模塊tempFile(對本例而言,我們的測驗模塊保存在'tempFile.js'檔案中),

describe('tempFile', () => {
  var tempFile;
  var sandbox;
  var err;

...

  有關這兩個變數的初始化和設定稍后我會講到,

  我們還需要為Node.js的fs模塊添加一個stub,如果你仔細查看我們的測驗代碼,你會發現基本上我們需要對fs模塊的三個屬性進行stub或者mock:1)fs.access;2)fs.unlinkSync;3)被fs.access使用的constants物件(F_OK和W_OK屬性),

  接著剛才的代碼,我們繼續添加對fs模塊的stub部分:

    var fsStub = {
        constants: {
            F_OK : 0,
            W_OK: 0
        },
        access: function(path, mode, cb) {
            cb(err, []);
        },
        unlinkSync: function(path) { }
    }

  這里有幾個需要注意的地方,首先,我們不用太關心constants物件中各個屬性的值具體是什么,因為我們只是對fs.access進行stub,我們將constants物件的兩個屬性的值都設定為'0',不論是fs.access還是fs.unlinkSync,它們的stub都可以正常作業,我們唯一需要額外處理的一點是,在fs.access的stub中呼叫回呼函式cb,正如你所看到的,這個回呼函式會接收我們測驗中的err變數并進行處理,

Setup和Teardown

  我們已經添加了所有的全域變數,現在開始添加beforeEachafterEach鉤子函式,它們將在每個單元測驗運行前和運行后自動執行,

    beforeEach((done) => {
        tempFile = rewire('../tempFile');
        tempFile.__set__({
            'fs': fsStub,
            'messaging': {
                printTempPdfMessage: function() {}
            }
        });

        sandbox = sinon.createSandbox();

        done();
    });

    afterEach((done) => {
        sandbox.restore();

        done();
    });

  在beforeEach函式中,我們使用Rewire匯入tempFile.js檔案,這里之所以沒有使用require而用rewire,是因為每次通過rewire匯入檔案時都會快取一個新的副本,這樣每次測驗時都會獲得一個干凈的版本,

  接下來,我們通過Rewire的__set__方法覆寫tempFile代碼中的fs方法和messaging,我們用上面創建的fsStub來替換fs,同時我們也替換了messaging,它其中的printTempPdfMessage是一個空函式,這樣做的一個好處是我們不需要通過其它的方式來阻止該函式中原本的Console.log陳述句的執行,這個被替換過的printTempPdfMessage函式仍然會被呼叫并執行,但它什么都不會做,

  然后,我們通過Sinon的createSandbox方法來為我們的測驗程式創建一個stubs,

  最后,在afterEach函式中,我們將sandbox進行恢復,以便其它的單元測驗繼續執行,

最后幾個單元測驗

  現在,我們準備完成剩下的幾個單元測驗,我們還是一個一個來看,

        it('should return true for "temppdf.md" existing, being writable and being deleted successfully', () => {
            var checkFileAccess = sandbox.stub();
            checkFileAccess.onCall(0).returns(2);
            checkFileAccess.onCall(1).returns(0);

            err = null;

            tempFile.__set__({
                'checkFileAccess': checkFileAccess
            });

            return tempFile.deleteTempPdf().should.eventually.be.true;
        })

  首先要做的是為私有方法checkFileAccess創建一個stub,這個sandbox是在beforeEach函式中初始化的,所以這里可以直接使用它來創建stub,這里我們不需要為這個方法提供任何實作邏輯,而只需要宣告它被呼叫時的回傳值,你應該已經注意到了,checkFileAccess第一次被呼叫時(索引為0)回傳值為'2',第二次被呼叫時我們規定回傳值為'0',這么做是為了驗證我們在deletedTempPdf方法中的if-then陳述句的邏輯,

  同時這里我們還將err變數設定為null,

  最后,結合在beforeEach函式中已經覆寫過的fs和messaging的printTempPdfMessage方法,我們又通過Rewire的__set__方法覆寫了tempFile中的checkFileAccess方法,同樣,這個覆寫過的方法不會做任何事情,它只會回傳我們設定的值,這就是Rewire神奇的地方,它允許我們通過stub覆寫私有方法,

  接下來是最后一個單元測驗:

        it('should return false for unknown error when checking access of "temppdf.md"', () => {
            var checkFileAccess = sandbox.stub();
            checkFileAccess.onCall(0).returns(3);

            tempFile.__set__({
                'checkFileAccess': checkFileAccess
            });

            return tempFile.deleteTempPdf().should.eventually.be.false;
        })

  它和前一個單元測驗的唯一區別是checkFileAccess方法被呼叫時的回傳值不同,因為這里我們將檢查deleteTempPdf方法如何根據checkFileAccess方法的回傳值來做出正確的回應,這里的回傳值是'3',而不是'0'、'1'或'2',這將促使deleteTempPdf方法進入到最后一個else分支中,

  現在,我們已經完成了所有的作業,并且我們的單元測驗代碼覆寫率可以達到100%,

  下面是完整的測驗代碼:

var chai = require('chai');
var chaiAsPromised = require('chai-as-promised');
chai.use(chaiAsPromised).should();
var sinon = require('sinon');
var rewire = require('rewire');


describe('tempFile', () => {
    var tempFile;
    var sandbox;
    var err;

    var fsStub = {
        constants: {
            F_OK : 0,
            W_OK: 0
        },
        access: function(path, mode, cb) {
            cb(err, []);
        },
        unlinkSync: function(path) { }
    }

    beforeEach((done) => {
        tempFile = rewire('../tempFile');
        tempFile.__set__({
            'fs': fsStub,
            'messaging': {
                printTempPdfMessage: function() {}
            }
        });

        sandbox = sinon.createSandbox();

        done();
    });

    afterEach((done) => {
        sandbox.restore();

        done();
    });

    describe('deleteTempPdf', () => {
        it('should return true for "temppdf.md" not existing', () => {
            err = { 
                code: 'ENOENT'
            };

            return tempFile.deleteTempPdf().should.eventually.be.true;
        })

        it('should return false for "temppdf.md" being readonly', () => {
            err = {
                code: 'SOMETHING_ELSE'
            };

            return tempFile.deleteTempPdf().should.eventually.be.false;
        })

        it('should return true for "temppdf.md" existing, being writable and being deleted successfully', () => {
            var checkFileAccess = sandbox.stub();
            checkFileAccess.onCall(0).returns(2);
            checkFileAccess.onCall(1).returns(0);

            err = null;

            tempFile.__set__({
                'checkFileAccess': checkFileAccess
            });

            return tempFile.deleteTempPdf().should.eventually.be.true;
        })

        it('should return false for "temppdf.md" existing, being writable, but not deleted successfully', () => {
            err = null;

            return tempFile.deleteTempPdf().should.eventually.be.false;
        })

        it('should return false for unknown error when checking access of "temppdf.md"', () => {
            var checkFileAccess = sandbox.stub();
            checkFileAccess.onCall(0).returns(3);

            tempFile.__set__({
                'checkFileAccess': checkFileAccess
            });

            return tempFile.deleteTempPdf().should.eventually.be.false;
        })
    })
})

  值得注意的是,如果你在單元測驗中使用諸如nyc或者istanbuljs等第三方庫來輔助輸出測驗結果并給出代碼覆寫率,你可能需要小心使用Rewire來覆寫你代碼中的私有方法,因為這類別庫的作業原理是基于require參考的,對于在測驗中使用rewire參考可能會影響最終的代碼覆寫率的準確度,不過這也不是絕對的,需要根據最終的使用情況來定,

原文地址:https://jdav.is/2019/01/29/using-sinonrewire-for-unit-testing-with-private-methods/

轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/375133.html

標籤:其他

上一篇:使用 Docker 部署 canal,并將訊息推送到 RabbitMQ

下一篇:2021熱乎的位元組跳動軟體測驗工程師面試題及答案分享

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 網閘典型架構簡述

    網閘架構一般分為兩種:三主機的三系統架構網閘和雙主機的2+1架構網閘。 三主機架構分別為內端機、外端機和仲裁機。三機無論從軟體和硬體上均各自獨立。首先從硬體上來看,三機都用各自獨立的主板、記憶體及存盤設備。從軟體上來看,三機有各自獨立的作業系統。這樣能達到完全的三機獨立。對于“2+1”系統,“2”分為 ......

    uj5u.com 2020-09-10 02:00:44 more
  • 如何從xshell上傳檔案到centos linux虛擬機里

    如何從xshell上傳檔案到centos linux虛擬機里及:虛擬機CentOs下執行 yum -y install lrzsz命令,出現錯誤:鏡像無法找到軟體包 前言 一、安裝lrzsz步驟 二、上傳檔案 三、遇到的問題及解決方案 總結 前言 提示:其實很簡單,往虛擬機上安裝一個上傳檔案的工具 ......

    uj5u.com 2020-09-10 02:00:47 more
  • 一、SQLMAP入門

    一、SQLMAP入門 1、判斷是否存在注入 sqlmap.py -u 網址/id=1 id=1不可缺少。當注入點后面的引數大于兩個時。需要加雙引號, sqlmap.py -u "網址/id=1&uid=1" 2、判斷文本中的請求是否存在注入 從文本中加載http請求,SQLMAP可以從一個文本檔案中 ......

    uj5u.com 2020-09-10 02:00:50 more
  • Metasploit 簡單使用教程

    metasploit 簡單使用教程 浩先生, 2020-08-28 16:18:25 分類專欄: kail 網路安全 linux 文章標簽: linux資訊安全 編輯 著作權 metasploit 使用教程 前言 一、Metasploit是什么? 二、準備作業 三、具體步驟 前言 Msfconsole ......

    uj5u.com 2020-09-10 02:00:53 more
  • 游戲逆向之驅動層與用戶層通訊

    驅動層代碼: #pragma once #include <ntifs.h> #define add_code CTL_CODE(FILE_DEVICE_UNKNOWN,0x800,METHOD_BUFFERED,FILE_ANY_ACCESS) /* 更多游戲逆向視頻www.yxfzedu.com ......

    uj5u.com 2020-09-10 02:00:56 more
  • 北斗電力時鐘(北斗授時服務器)讓網路資料更精準

    北斗電力時鐘(北斗授時服務器)讓網路資料更精準 北斗電力時鐘(北斗授時服務器)讓網路資料更精準 京準電子科技官微——ahjzsz 近幾年,資訊技術的得了快速發展,互聯網在逐漸普及,其在人們生活和生產中都得到了廣泛應用,并且取得了不錯的應用效果。計算機網路資訊在電力系統中的應用,一方面使電力系統的運行 ......

    uj5u.com 2020-09-10 02:01:03 more
  • 【CTF】CTFHub 技能樹 彩蛋 writeup

    ?碎碎念 CTFHub:https://www.ctfhub.com/ 筆者入門CTF時時剛開始刷的是bugku的舊平臺,后來才有了CTFHub。 感覺不論是網頁UI設計,還是題目質量,賽事跟蹤,工具軟體都做得很不錯。 而且因為獨到的金幣制度的確讓人有一種想去刷題賺金幣的感覺。 個人還是非常喜歡這個 ......

    uj5u.com 2020-09-10 02:04:05 more
  • 02windows基礎操作

    我學到了一下幾點 Windows系統目錄結構與滲透的作用 常見Windows的服務詳解 Windows埠詳解 常用的Windows注冊表詳解 hacker DOS命令詳解(net user / type /md /rd/ dir /cd /net use copy、批處理 等) 利用dos命令制作 ......

    uj5u.com 2020-09-10 02:04:18 more
  • 03.Linux基礎操作

    我學到了以下幾點 01Linux系統介紹02系統安裝,密碼啊破解03Linux常用命令04LAMP 01LINUX windows: win03 8 12 16 19 配置不繁瑣 Linux:redhat,centos(紅帽社區版),Ubuntu server,suse unix:金融機構,證券,銀 ......

    uj5u.com 2020-09-10 02:04:30 more
  • 05HTML

    01HTML介紹 02頭部標簽講解03基礎標簽講解04表單標簽講解 HTML前段語言 js1.了解代碼2.根據代碼 懂得挖掘漏洞 (POST注入/XSS漏洞上傳)3.黑帽seo 白帽seo 客戶網站被黑帽植入劫持代碼如何處理4.熟悉html表單 <html><head><title>TDK標題,描述 ......

    uj5u.com 2020-09-10 02:04:36 more
最新发布
  • 2023年最新微信小程式抓包教程

    01 開門見山 隔一個月發一篇文章,不過分。 首先回顧一下《微信系結手機號資料庫被脫庫事件》,我也是第一時間得知了這個訊息,然后跟蹤了整件事情的經過。下面是這起事件的相關截圖以及近日流出的一萬條資料樣本: 個人認為這件事也沒什么,還不如關注一下之前45億快遞資料查詢渠道疑似在近日復活的訊息。 訊息是 ......

    uj5u.com 2023-04-20 08:48:24 more
  • web3 產品介紹:metamask 錢包 使用最多的瀏覽器插件錢包

    Metamask錢包是一種基于區塊鏈技術的數字貨幣錢包,它允許用戶在安全、便捷的環境下管理自己的加密資產。Metamask錢包是以太坊生態系統中最流行的錢包之一,它具有易于使用、安全性高和功能強大等優點。 本文將詳細介紹Metamask錢包的功能和使用方法。 一、 Metamask錢包的功能 數字資 ......

    uj5u.com 2023-04-20 08:47:46 more
  • vulnhub_Earth

    前言 靶機地址->>>vulnhub_Earth 攻擊機ip:192.168.20.121 靶機ip:192.168.20.122 參考文章 https://www.cnblogs.com/Jing-X/archive/2022/04/03/16097695.html https://www.cnb ......

    uj5u.com 2023-04-20 07:46:20 more
  • 從4k到42k,軟體測驗工程師的漲薪史,給我看哭了

    清明節一過,盲猜大家已經無心上班,在數著日子準備過五一,但一想到銀行卡里的余額……瞬間心情就不美麗了。最近,2023年高校畢業生就業調查顯示,本科畢業月平均起薪為5825元。調查一出,便有很多同學表示自己又被平均了。看著這一資料,不免讓人想到前不久中國青年報的一項調查:近六成大學生認為畢業10年內會 ......

    uj5u.com 2023-04-20 07:44:00 more
  • 最新版本 Stable Diffusion 開源 AI 繪畫工具之中文自動提詞篇

    🎈 標簽生成器 由于輸入正向提示詞 prompt 和反向提示詞 negative prompt 都是使用英文,所以對學習母語的我們非常不友好 使用網址:https://tinygeeker.github.io/p/ai-prompt-generator 這個網址是為了讓大家在使用 AI 繪畫的時候 ......

    uj5u.com 2023-04-20 07:43:36 more
  • 漫談前端自動化測驗演進之路及測驗工具分析

    隨著前端技術的不斷發展和應用程式的日益復雜,前端自動化測驗也在不斷演進。隨著 Web 應用程式變得越來越復雜,自動化測驗的需求也越來越高。如今,自動化測驗已經成為 Web 應用程式開發程序中不可或缺的一部分,它們可以幫助開發人員更快地發現和修復錯誤,提高應用程式的性能和可靠性。 ......

    uj5u.com 2023-04-20 07:43:16 more
  • CANN開發實踐:4個DVPP記憶體問題的典型案例解讀

    摘要:由于DVPP媒體資料處理功能對存放輸入、輸出資料的記憶體有更高的要求(例如,記憶體首地址128位元組對齊),因此需呼叫專用的記憶體申請介面,那么本期就分享幾個關于DVPP記憶體問題的典型案例,并給出原因分析及解決方法。 本文分享自華為云社區《FAQ_DVPP記憶體問題案例》,作者:昇騰CANN。 DVPP ......

    uj5u.com 2023-04-20 07:43:03 more
  • msf學習

    msf學習 以kali自帶的msf為例 一、msf核心模塊與功能 msf模塊都放在/usr/share/metasploit-framework/modules目錄下 1、auxiliary 輔助模塊,輔助滲透(埠掃描、登錄密碼爆破、漏洞驗證等) 2、encoders 編碼器模塊,主要包含各種編碼 ......

    uj5u.com 2023-04-20 07:42:59 more
  • Halcon軟體安裝與界面簡介

    1. 下載Halcon17版本到到本地 2. 雙擊安裝包后 3. 步驟如下 1.2 Halcon軟體安裝 界面分為四大塊 1. Halcon的五個助手 1) 影像采集助手:與相機連接,設定相機引數,采集影像 2) 標定助手:九點標定或是其它的標定,生成標定檔案及內參外參,可以將像素單位轉換為長度單位 ......

    uj5u.com 2023-04-20 07:42:17 more
  • 在MacOS下使用Unity3D開發游戲

    第一次發博客,先發一下我的游戲開發環境吧。 去年2月份買了一臺MacBookPro2021 M1pro(以下簡稱mbp),這一年來一直在用mbp開發游戲。我大致分享一下我的開發工具以及使用體驗。 1、Unity 官網鏈接: https://unity.cn/releases 我一般使用的Apple ......

    uj5u.com 2023-04-20 07:40:19 more