0

我了解单元测试独立函数(如帮助程序类)的知识,但我如何处理非独立函数,通常在具有多个验证和结果的类文件中?

下面的示例显示了不同结果中的多个条件检查和响应。

  • 我是否在我的测试用例中调用valueCheckand函数?proceedApiCheck但是在测试中我不需要不同的场景或操作。(例如,setState / 导航)
  • 我是否在我的测试用例中valueCheck编写了一个新函数?proceedApiCheck但这意味着我的代码中有两种不同的逻辑。有一天,如果我在应用程序中更改逻辑,我的测试用例不会失败,因为它指的是旧逻辑。

你们中的任何人都可以对此有所了解吗?

例子

export class Screen1 extends React.Component {

    valueCheck = (value) => {
        if(value === 'abc'){
            this.setState({ isNavigating:true, transfer: true })
            this.proceedApiCheck(value)
        }
        if(value === '123'){
            this.setState({ isNavigating:true, transfer: false })
            this.proceedApiCheck(value)
        }
    }

    proceedApiCheck = async(value) =>{
        let data
        try{
            data = await FirstApi(value);
            this.setState(data)
        }catch(){
            this.navigateToScreen('Failure')
            return;
        }

        switch(data.name){
            case 'fake adidas':
                this.navigateToScreen('Failure')
                return;
            case 'fake nike':
                this.navigateToScreen('Failure')
                return; 
        }
        
        try{
            const result = await secondApi(data.price);

            switch(result.currency){
                case 'EURO':
                    this.navigateToScreen('Euro')
                case 'Pound':
                    this.navigateToScreen('Pound')
                default: 
                    this.navigateToScreen('Dollar')
            }
        }catch(){
            this.navigateToScreen('Failure')
            return;
        }
    }


}
4

2 回答 2

1

你有了一个有价值的发现:

最直接的代码编写方式不一定是最健壮的代码编写方式。

大多数人使用类的方式加剧了这个不幸的问题:隐式this引用使得在一个方法中做太多事情变得很容易,更不用说一个类了。这使您获得了第二个有价值的发现:

隔离单元测试最有价值的事情之一是它们可以为您提供有关您的设计的反馈。

在这里,隔离测试的困难是告诉你你已经以一种难以梳理的方式耦合了不同的关注点。您有一个具有七个 (7!!) 不同退出点的方法,但现在您被卡住了,因为尝试触发适当的逻辑以确保您击中所有退出点需要进行大量模拟。

考虑以下替代方案:

const FAIL = {}; // could also use Symbol() here, any unique ref
const BAD_NAMES = ['fake whatever'];
async function apiCall1() {
  const resp = await fetch(someURL);
  return resp.json();
}

function validate1(data) {
  return data?.name === undefined || BAD_NAMES.includes(data?.name) 
    ? FAIL 
    : data.name;
}

// You can imagine what validation and fetching look like for
// the second API call

function processData(data) {
  switch(data.currency){
    case 'EURO':
      return 'Euro';
    case 'Pound':
      return 'Pound';
    default: 
      return 'Dollar';
  }
}

async function doTheThing() { // use a better name IRL
  try {
    const first = await apiCall1();
    const data = validate1(first);
    if (data === FAIL) throw new Error('whatever');
    
    const second = await apiCall2(data.whatever);
    const data2 = validate2(second);
    if (data2 === FAIL) throw new Error('something else');

    // process data we now know is good.
    return processData(data);
  } catch (err) {
    console.error(err);
    return 'Failure';
  }
}

class Screen1 extends React.Component {
  async proceedApiCheck () {
    const nextScreen = await doTheThing();
    this.navigateToScreen(nextScreen);
  }
}
      
  1. 这里我们只在一个地方返回失败屏幕触发器。这些功能都有明确的出口。
  2. 有一个方便的功能可以取消装箱请求(或者隐藏 xhr 的详细信息,如果这是您滚动的方式)。
  3. 所有验证和业务逻辑都是可独立测试的。它也是在一个地方,而不是以冗长的方法分散在各个地方。
  4. 所有逻辑都在返回值的普通函数中,除了 for 之外不需要任何模拟fetch
  5. proceedApiCheck唯一要做的就是从逻辑函数中获取值并导航到正确的屏幕,并且很容易模拟测试。

你可以,如果你真的喜欢类,也可以让你的类的所有这些函数成为静态方法,如果你这样做的话,但重要的是你的测试几乎都不需要复杂的模拟,它应该更明显代码路径是什么以及如何测试它们。

您要遵守的高风险软件工程实践(正如@DrewReese 在评论中指出的那样)是单一职责原则,它指出函数/方法应该执行一个逻辑操作。验证器仅进行验证,条件仅进行分派,从外部源获取数据的函数应仅获取数据(而不是对其进行操作)等。

于 2021-08-10T02:32:25.383 回答
0

如果您认为有必要进行单元测试valueCheck(在实际情况下可能是一个复杂的功能),请将其移出类并使其可单独测试。那么它应该是这样的:

function valueCheck(value) {
switch(value) {
case 'abc':
    return {shouldProceedApiCheck: true, newState:{ isNavigating:true, transfer: true }}
case '123':
    return {shouldProceedApiCheck: true, { isNavigating:true, transfer: false }}
default:
    return {shouldProceedApiCheck: false, newState: {} }
}

.....

class Screen1 extends React.Component {
....
whenToCall = ()=>{
    const {newState, shouldProceedApiCheck} = valueCheck(value)
    this.setState(valueCheck(newState), ()=>{
        if(shouldProceedApiCheck) {
          this.proceedApiCheck(value)
        }
    })
}
....
}

进一步阅读:https ://medium.com/front-end-weekly/making-testable-javascript-code-2a71afba5120

于 2021-08-10T02:10:43.723 回答